mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
99ca79ac7d | ||
![]() |
24564f4c74 | ||
![]() |
212c802a1e | ||
![]() |
984b5d6c40 | ||
![]() |
0e3a4191a9 | ||
![]() |
570a34bca5 | ||
![]() |
c9a0c29286 | ||
![]() |
b5f804ec22 | ||
![]() |
dadbb83271 |
17
README.md
17
README.md
@@ -3,36 +3,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
## To Be Continued
|
## 欢迎回来
|
||||||
当前版本 (v2.1.x) 请使用内核构建版本(版本号最后的五位数)为 27187 以上的 PC NTQQ 运行。
|
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||||
|
|
||||||
## 项目介绍
|
## 猫猫技能
|
||||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
|
||||||
|
|
||||||
## 项目优势
|
|
||||||
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||||
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||||
- [x] **超多接口**:在实现大部分Onebot接口上扩展了一套私有API
|
- [x] **超多接口**:在实现大部分Onebot接口上扩展了一套私有API
|
||||||
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
||||||
|
|
||||||
## 如何使用
|
## 使用猫猫
|
||||||
|
|
||||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||||
|
|
||||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||||
|
|
||||||
## 相关链接
|
## 回家旅途
|
||||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
|
|
||||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||||
|
|
||||||
## 鸣谢名单
|
## 猫猫朋友
|
||||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
|
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
|
||||||
|
|
||||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 使用许可
|
## 约法三章
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "2.2.4",
|
"version": "2.2.5",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.2.4",
|
"version": "2.2.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "vite build --mode framework",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "vite build --mode shell",
|
||||||
|
@@ -162,7 +162,89 @@ export class LegacyNTEventWrapper {
|
|||||||
this.createListenerFunction(ListenerMainName);
|
this.createListenerFunction(ListenerMainName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async CallNormalEventV2<
|
||||||
|
EventType extends (...args: any[]) => Promise<any>,
|
||||||
|
ListenerType extends (...args: any[]) => void
|
||||||
|
>(
|
||||||
|
EventName = '',
|
||||||
|
ListenerName = '',
|
||||||
|
waitTimes = 1,
|
||||||
|
timeout: number = 3000,
|
||||||
|
checkerEvent: (ret: Awaited<ReturnType<EventType>>) => boolean = () => true,
|
||||||
|
checkerListener: (...args: Parameters<ListenerType>) => boolean = () => true,
|
||||||
|
...args: Parameters<EventType>
|
||||||
|
) {
|
||||||
|
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||||
|
async (resolve, reject) => {
|
||||||
|
const id = randomUUID();
|
||||||
|
let complete = 0;
|
||||||
|
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||||
|
let retEvent: any = {};
|
||||||
|
const databack = () => {
|
||||||
|
if (complete == 0) {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
'Timeout: NTEvent EventName:' +
|
||||||
|
EventName +
|
||||||
|
' ListenerName:' +
|
||||||
|
ListenerName +
|
||||||
|
' EventRet:\n' +
|
||||||
|
JSON.stringify(retEvent, null, 4) +
|
||||||
|
'\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListenerNameList = ListenerName.split('/');
|
||||||
|
const ListenerMainName = ListenerNameList[0];
|
||||||
|
const ListenerSubName = ListenerNameList[1];
|
||||||
|
|
||||||
|
const Timeouter = setTimeout(databack, timeout);
|
||||||
|
|
||||||
|
const eventCallbak = {
|
||||||
|
timeout: timeout,
|
||||||
|
createtime: Date.now(),
|
||||||
|
checker: checkerListener,
|
||||||
|
func: (...args: any[]) => {
|
||||||
|
complete++;
|
||||||
|
//console.log('func', ...args);
|
||||||
|
retData = args as Parameters<ListenerType>;
|
||||||
|
if (complete >= waitTimes) {
|
||||||
|
clearTimeout(Timeouter);
|
||||||
|
databack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (!this.EventTask.get(ListenerMainName)) {
|
||||||
|
this.EventTask.set(ListenerMainName, new Map());
|
||||||
|
}
|
||||||
|
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||||
|
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||||
|
}
|
||||||
|
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||||
|
this.createListenerFunction(ListenerMainName);
|
||||||
|
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||||
|
retEvent = await EventFunc!(...(args as any[]));
|
||||||
|
if(!checkerEvent(retEvent)){
|
||||||
|
clearTimeout(Timeouter);
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
'EventChecker Failed: NTEvent EventName:' +
|
||||||
|
EventName +
|
||||||
|
' ListenerName:' +
|
||||||
|
ListenerName +
|
||||||
|
' EventRet:\n' +
|
||||||
|
JSON.stringify(retEvent, null, 4) +
|
||||||
|
'\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
async CallNormalEvent<
|
async CallNormalEvent<
|
||||||
EventType extends (...args: any[]) => Promise<any>,
|
EventType extends (...args: any[]) => Promise<any>,
|
||||||
ListenerType extends (...args: any[]) => void
|
ListenerType extends (...args: any[]) => void
|
||||||
|
@@ -2,7 +2,7 @@ import path, { dirname } from 'path';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
export const napcat_version = '2.2.4';
|
export const napcat_version = '2.2.5';
|
||||||
|
|
||||||
export class NapCatPathWrapper {
|
export class NapCatPathWrapper {
|
||||||
binaryPath: string;
|
binaryPath: string;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Friend, FriendV2, User } from '@/core/entities';
|
import { Friend, FriendV2, User } from '@/core/entities';
|
||||||
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
|
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelBuddyListener, NodeIKernelBuddyService, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
|
||||||
import { LimitedHashTable } from '@/common/utils/MessageUnique';
|
import { LimitedHashTable } from '@/common/utils/MessageUnique';
|
||||||
|
|
||||||
export class NTQQFriendApi {
|
export class NTQQFriendApi {
|
||||||
@@ -70,29 +70,14 @@ export class NTQQFriendApi {
|
|||||||
async isBuddy(uid: string) {
|
async isBuddy(uid: string) {
|
||||||
return this.context.session.getBuddyService().isBuddy(uid);
|
return this.context.session.getBuddyService().isBuddy(uid);
|
||||||
}
|
}
|
||||||
|
async clearBuddyReqUnreadCnt() {
|
||||||
/**
|
return this.context.session.getBuddyService().clearBuddyReqUnreadCnt();
|
||||||
* @deprecated
|
|
||||||
* @param forced
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async getFriends(forced = false): Promise<User[]> {
|
|
||||||
const [_retData, _BuddyArg] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>
|
|
||||||
(
|
|
||||||
'NodeIKernelBuddyService/getBuddyList',
|
|
||||||
'NodeIKernelBuddyListener/onBuddyListChange',
|
|
||||||
1,
|
|
||||||
5000,
|
|
||||||
() => true,
|
|
||||||
forced,
|
|
||||||
);
|
|
||||||
const friends: User[] = [];
|
|
||||||
for (const categoryItem of _BuddyArg) {
|
|
||||||
for (const friend of categoryItem.buddyList) {
|
|
||||||
friends.push(friend);
|
|
||||||
}
|
}
|
||||||
}
|
async getBuddyReq() {
|
||||||
return friends;
|
const [, ret] = await this.core.eventWrapper.CallNormalEventV2
|
||||||
|
<NodeIKernelBuddyService['getBuddyReq'], NodeIKernelBuddyListener['onBuddyReqChange']>
|
||||||
|
('NodeIKernelBuddyService/getBuddyReq', 'NodeIKernelBuddyListener/onBuddyReqChange', 1, 5000);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleFriendRequest(flag: string, accept: boolean) {
|
async handleFriendRequest(flag: string, accept: boolean) {
|
||||||
|
@@ -64,6 +64,7 @@ export enum BuddyReqType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FriendRequest {
|
export interface FriendRequest {
|
||||||
|
isBuddy?: boolean;
|
||||||
isInitiator?: boolean;
|
isInitiator?: boolean;
|
||||||
isDecide: boolean;
|
isDecide: boolean;
|
||||||
friendUid: string;
|
friendUid: string;
|
||||||
|
@@ -61,11 +61,11 @@ export interface NodeIKernelBuddyService {
|
|||||||
|
|
||||||
getBuddyReqUnreadCnt(): number;
|
getBuddyReqUnreadCnt(): number;
|
||||||
|
|
||||||
getBuddyReq(): unknown;
|
getBuddyReq(): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
delBuddyReq(uid: number): void;
|
delBuddyReq(uid: number): void;
|
||||||
|
|
||||||
clearBuddyReqUnreadCnt(): void;
|
clearBuddyReqUnreadCnt(): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
reqToAddFriends(uid: number, msg: string): void;
|
reqToAddFriends(uid: number, msg: string): void;
|
||||||
|
|
||||||
|
@@ -171,7 +171,7 @@ const _handlers: {
|
|||||||
} else {
|
} else {
|
||||||
postData = data;
|
postData = data;
|
||||||
}
|
}
|
||||||
// Mlikiowa V2.2.4 Refactor Todo
|
// Mlikiowa V2.2.5 Refactor Todo
|
||||||
const signUrl = obContext.configLoader.configData.musicSignUrl;
|
const signUrl = obContext.configLoader.configData.musicSignUrl;
|
||||||
if (!signUrl) {
|
if (!signUrl) {
|
||||||
if (data.type === 'qq') {
|
if (data.type === 'qq') {
|
||||||
|
@@ -90,6 +90,7 @@ async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg,
|
|||||||
// This redundant design of Ob11 here should be blamed.
|
// This redundant design of Ob11 here should be blamed.
|
||||||
const NTQQFriendApi = coreContext.apis.FriendApi;
|
const NTQQFriendApi = coreContext.apis.FriendApi;
|
||||||
const NTQQUserApi = coreContext.apis.UserApi;
|
const NTQQUserApi = coreContext.apis.UserApi;
|
||||||
|
const NTQQMsgApi = coreContext.apis.MsgApi;
|
||||||
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
|
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
|
||||||
return {
|
return {
|
||||||
chatType: ChatType.KCHATTYPEGROUP,
|
chatType: ChatType.KCHATTYPEGROUP,
|
||||||
@@ -98,12 +99,34 @@ async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg,
|
|||||||
}
|
}
|
||||||
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
||||||
const Uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
|
const Uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
|
if (!Uid) throw '无法获取用户信息';
|
||||||
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy);
|
const isBuddy = await NTQQFriendApi.isBuddy(Uid);
|
||||||
|
if (!isBuddy) {
|
||||||
|
const ret = await NTQQMsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid);
|
||||||
|
if (ret.tmpChatInfo?.groupCode) {
|
||||||
return {
|
return {
|
||||||
chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||||
|
peerUid: Uid,
|
||||||
|
guildId: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (payload.group_id) {
|
||||||
|
return {
|
||||||
|
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||||
|
peerUid: Uid,
|
||||||
|
guildId: payload.group_id.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
chatType: ChatType.KCHATTYPEC2C,
|
||||||
peerUid: Uid!,
|
peerUid: Uid!,
|
||||||
guildId: payload.group_id?.toString() || '',
|
guildId: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
chatType: ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: Uid!,
|
||||||
|
guildId: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw '请指定 group_id 或 user_id';
|
throw '请指定 group_id 或 user_id';
|
||||||
|
@@ -65,11 +65,12 @@ export class OneBotMsgApi {
|
|||||||
name = content.replace('@', '');
|
name = content.replace('@', '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message_data = {
|
message_data = {
|
||||||
type: OB11MessageDataType.at,
|
type: OB11MessageDataType.at,
|
||||||
data: {
|
data: {
|
||||||
qq: qq!,
|
qq: qq!,
|
||||||
name,
|
//name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return message_data;
|
return message_data;
|
||||||
|
@@ -283,8 +283,16 @@ export class NapCatOneBot11Adapter {
|
|||||||
private initBuddyListener() {
|
private initBuddyListener() {
|
||||||
const buddyListener = new BuddyListener();
|
const buddyListener = new BuddyListener();
|
||||||
|
|
||||||
buddyListener.onBuddyReqChange = reqs => {
|
buddyListener.onBuddyReqChange = async reqs => {
|
||||||
reqs.buddyReqs.forEach(async req => {
|
this.core.apis.FriendApi.clearBuddyReqUnreadCnt();
|
||||||
|
for (let i = 0; i < reqs.unreadNums; i++) {
|
||||||
|
const req = reqs.buddyReqs[i];
|
||||||
|
//req.isBuddy === false是单向好友 null为常规情况
|
||||||
|
// if (req.isBuddy === false && ) {
|
||||||
|
// const NTQQFriendApi = this.core.apis.FriendApi;
|
||||||
|
// await NTQQFriendApi.handleFriendRequest(req.friendUid + '|' + req.reqTime, true);
|
||||||
|
// }
|
||||||
|
|
||||||
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
|
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,7 +307,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logDebug('获取加好友者QQ号失败', e);
|
this.context.logger.logDebug('获取加好友者QQ号失败', e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.context.session.getBuddyService().addKernelBuddyListener(
|
this.context.session.getBuddyService().addKernelBuddyListener(
|
||||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
undefined,
|
undefined,
|
||||||
SettingButton('V2.2.4', 'napcat-update-button', 'secondary'),
|
SettingButton('V2.2.5', 'napcat-update-button', 'secondary'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
void 0,
|
void 0,
|
||||||
SettingButton("V2.2.4", "napcat-update-button", "secondary")
|
SettingButton("V2.2.5", "napcat-update-button", "secondary")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
Reference in New Issue
Block a user