mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e7222653fa | ||
![]() |
014f0758f5 | ||
![]() |
0e8b416f6d | ||
![]() |
09a60a2204 | ||
![]() |
b0eae307c2 | ||
![]() |
f5d2b54cca | ||
![]() |
3eefec3899 | ||
![]() |
b6a8094554 | ||
![]() |
4083b35436 | ||
![]() |
bb72d70baf | ||
![]() |
95d1a77f52 | ||
![]() |
051729886e | ||
![]() |
0f00123dc7 | ||
![]() |
0b0a089d86 | ||
![]() |
c711a7d99a | ||
![]() |
43f1d8c88c | ||
![]() |
e818e79d20 | ||
![]() |
cbad3ff1de | ||
![]() |
16a2e5e996 | ||
![]() |
331c6a50d0 | ||
![]() |
31c4540ec6 | ||
![]() |
1e6116554f |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.15-28788",
|
||||
"version": "9.9.16-28788",
|
||||
"verHash": "73b0c8f6",
|
||||
"linuxVersion": "3.2.12-28788",
|
||||
"linuxVersion": "3.2.13-28788",
|
||||
"linuxVerHash": "55fb6434",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -23,4 +23,4 @@
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.6",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.6",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
@@ -49,4 +49,4 @@
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '3.0.0';
|
||||
export const napCatVersion = '3.0.6';
|
||||
|
@@ -11,7 +11,7 @@ export class NTQQFriendApi {
|
||||
this.core = core;
|
||||
}
|
||||
async setBuddyRemark(uid: string, remark: string) {
|
||||
return this.context.session.getBuddyService().setBuddyRemark(uid, remark);
|
||||
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
|
||||
}
|
||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import * as os from 'os';
|
||||
import {ChatType, InstanceContext, NapCatCore} from '..';
|
||||
import { ChatType, InstanceContext, NapCatCore } from '..';
|
||||
import offset from '@/core/external/offset.json';
|
||||
import {PacketClient, RecvPacketData} from '@/core/packet/client';
|
||||
import {PacketSession} from "@/core/packet/session";
|
||||
import {PacketHexStr} from "@/core/packet/packer";
|
||||
import {NapProtoMsg} from '@/core/packet/proto/NapProto';
|
||||
import {OidbSvcTrpcTcp0X9067_202_Rsp_Body} from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||
import {OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp} from '@/core/packet/proto/oidb/OidbBase';
|
||||
import {OidbSvcTrpcTcp0XFE1_2RSP} from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
import {LogWrapper} from "@/common/log";
|
||||
import {SendLongMsgResp} from "@/core/packet/proto/message/action";
|
||||
import {PacketMsg} from "@/core/packet/msg/message";
|
||||
import {OidbSvcTrpcTcp0x6D6Response} from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import {PacketMsgPicElement} from "@/core/packet/msg/element";
|
||||
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
import { PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { PacketMsgPicElement } from "@/core/packet/msg/element";
|
||||
import { c } from 'vite/dist/node/types.d-aGj9QkWt';
|
||||
|
||||
interface OffsetType {
|
||||
[key: string]: {
|
||||
@@ -59,8 +60,12 @@ export class NTQQPacketApi {
|
||||
if (!table) return false;
|
||||
const url = 'ws://' + this.serverUrl + '/ws';
|
||||
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
|
||||
await this.packetSession.client.connect();
|
||||
await this.packetSession.client.init(process.pid, table.recv, table.send);
|
||||
const cb = () => {
|
||||
if (this.packetSession && this.packetSession.client) {
|
||||
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
||||
}
|
||||
}
|
||||
await this.packetSession.client.connect(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,8 +73,8 @@ export class NTQQPacketApi {
|
||||
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
||||
}
|
||||
|
||||
async sendPokePacket(group: number, peer: number) {
|
||||
const data = this.packetSession?.packer.packPokePacket(group, peer);
|
||||
async sendPokePacket(peer: number, group?: number) {
|
||||
const data = this.packetSession?.packer.packPokePacket(peer, group);
|
||||
await this.sendPacket('OidbSvcTrpcTcp.0xed3_1', data!, false);
|
||||
}
|
||||
|
||||
@@ -106,11 +111,11 @@ export class NTQQPacketApi {
|
||||
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
|
||||
}
|
||||
|
||||
private async uploadResources(msg: PacketMsg[], groupUin: number = 0){
|
||||
private async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||
const reqList = []
|
||||
for (const m of msg){
|
||||
for (const e of m.msg){
|
||||
if (e instanceof PacketMsgPicElement){
|
||||
for (const m of msg) {
|
||||
for (const e of m.msg) {
|
||||
if (e instanceof PacketMsgPicElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: String(groupUin) ? String(groupUin) : this.core.selfInfo.uid
|
||||
@@ -135,7 +140,7 @@ export class NTQQPacketApi {
|
||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x6d6_2', data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
|
||||
if (resp.download.retCode !== 0){
|
||||
if (resp.download.retCode !== 0) {
|
||||
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
|
||||
}
|
||||
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`
|
||||
|
@@ -517,6 +517,12 @@ export enum AtType {
|
||||
atAll = 1,
|
||||
atUser = 2
|
||||
}
|
||||
export enum MsgSourceType {
|
||||
K_DOWN_SOURCETYPE_AIOINNER = 1,
|
||||
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
|
||||
K_DOWN_SOURCETYPE_HISTORY = 3,
|
||||
K_DOWN_SOURCETYPE_UNKNOWN = 0
|
||||
}
|
||||
|
||||
// 来自Android分析
|
||||
export enum ChatType {
|
||||
@@ -874,6 +880,8 @@ export interface RawMessage {
|
||||
/**
|
||||
* 扩展字段,与 Ob11 msg ID 有关
|
||||
*/
|
||||
|
||||
|
||||
id?: number;
|
||||
|
||||
guildId: string;
|
||||
@@ -950,6 +958,8 @@ export interface RawMessage {
|
||||
records: RawMessage[];
|
||||
|
||||
elements: MessageElement[];
|
||||
|
||||
sourceType: MsgSourceType;
|
||||
}
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer;
|
||||
|
@@ -22,7 +22,7 @@ export class PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private isConnected: boolean = false;
|
||||
private reconnectAttempts: number = 0;
|
||||
private readonly maxReconnectAttempts: number = 5;//现在暂时不可配置
|
||||
private readonly maxReconnectAttempts: number = 60;//现在暂时不可配置
|
||||
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
private readonly clientUrl: string = '';
|
||||
readonly napCatCore: NapCatCore;
|
||||
@@ -47,16 +47,17 @@ export class PacketClient {
|
||||
return text;
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
connect(cb: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
||||
this.websocket = new WebSocket(this.clientUrl);
|
||||
this.websocket.on('error', (err) => {}/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
||||
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
||||
|
||||
this.websocket.onopen = () => {
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.logger.log.bind(this.logger)(`[Core] [Packet Server] Connected to ${this.clientUrl}`);
|
||||
cb();
|
||||
resolve();
|
||||
};
|
||||
|
||||
@@ -74,17 +75,17 @@ export class PacketClient {
|
||||
this.websocket.onclose = () => {
|
||||
this.isConnected = false;
|
||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||
this.attemptReconnect();
|
||||
this.attemptReconnect(cb);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private attemptReconnect(): void {
|
||||
private attemptReconnect(cb: any): void {
|
||||
try {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this.connect().catch((error) => {
|
||||
this.connect(cb).catch((error) => {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Reconnecting attempt failed,${error.message}`);
|
||||
});
|
||||
}, 5000 * this.reconnectAttempts);
|
||||
|
@@ -49,8 +49,8 @@ export class PacketHighwaySession {
|
||||
|
||||
private async checkAvailable() {
|
||||
if (!this.packetClient.available) {
|
||||
this.logger.logError('[Highway] packetClient not available!');
|
||||
throw new Error('packetClient not available!');
|
||||
this.logger.logError('[Highway] packetServer not available!');
|
||||
throw new Error('packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
|
||||
}
|
||||
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
||||
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
|
||||
|
@@ -363,6 +363,10 @@ export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgEl
|
||||
this.message = message ?? [];
|
||||
}
|
||||
|
||||
get isGroupMsg(): boolean {
|
||||
return this.message.some(msg => msg.groupId !== undefined);
|
||||
}
|
||||
|
||||
get JSON() {
|
||||
const id = crypto.randomUUID();
|
||||
return {
|
||||
@@ -387,7 +391,11 @@ export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgEl
|
||||
text: `${packetMsg.senderName}: ${packetMsg.msg.map(msg => msg.toPreview()).join('')}`,
|
||||
})),
|
||||
resid: this.resid,
|
||||
source: "聊天记录", // TODO:
|
||||
source: this.isGroupMsg ? "群聊的聊天记录" :
|
||||
this.message.length
|
||||
? Array.from(new Set(this.message.map(msg => msg.senderName)))
|
||||
.join('和') + '的聊天记录'
|
||||
: '聊天记录',
|
||||
summary: `查看${this.message.length}条转发消息`,
|
||||
uniseq: id,
|
||||
}
|
||||
|
@@ -47,11 +47,11 @@ export class PacketPacker {
|
||||
});
|
||||
}
|
||||
|
||||
packPokePacket(group: number, peer: number): PacketHexStr {
|
||||
packPokePacket(peer: number, group?: number): PacketHexStr {
|
||||
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
||||
uin: peer,
|
||||
groupUin: group,
|
||||
friendUin: group,
|
||||
friendUin: group ?? peer,
|
||||
ext: 0
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0xed3, 1, oidb_0xed3));
|
||||
@@ -114,9 +114,7 @@ export class PacketPacker {
|
||||
}
|
||||
}
|
||||
);
|
||||
this.logger.logDebug("packUploadForwardMsg LONGMSGRESULT!!!", this.toHexStr(longMsgResultData));
|
||||
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
|
||||
// this.logger.logDebug("packUploadForwardMsg PAYLOAD!!!", payload);
|
||||
const req = new NapProtoMsg(SendLongMsgReq).encode(
|
||||
{
|
||||
info: {
|
||||
|
@@ -36,7 +36,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
getBuddyRemark(uid: number): string;
|
||||
|
||||
setBuddyRemark(uid: string, remark: string): void;
|
||||
setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void;
|
||||
|
||||
getAvatarUrl(uid: number): string;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
|
||||
// no_cache get时传字符串
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -12,14 +12,11 @@ const SchemaData = {
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class GetUserStatus extends BaseAction<Payload, { status: number; ext_status: number; } | undefined> {
|
||||
export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: number; ext_status: number; } | undefined> {
|
||||
actionName = ActionName.GetUserStatus;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
if (!this.core.apis.PacketApi?.available) {
|
||||
throw new Error('PacketClient is not init');
|
||||
}
|
||||
return await this.core.apis.PacketApi.sendStatusPacket(+payload.user_id);
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,6 @@ export class GroupPoke extends GetPacketStatusDepends<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
await this.core.apis.PacketApi.sendPokePacket(+payload.group_id, +payload.user_id);
|
||||
await this.core.apis.PacketApi.sendPokePacket(+payload.user_id, +payload.group_id);
|
||||
}
|
||||
}
|
||||
|
@@ -91,6 +91,7 @@ import { GetGroupShutList } from './group/GetGroupShutList';
|
||||
import { GetGroupMemberList } from './group/GetGroupMemberList';
|
||||
import { GetGroupFileUrl } from "@/onebot/action/file/GetGroupFileUrl";
|
||||
import {GetPacketStatus} from "@/onebot/action/packet/GetPacketStatus";
|
||||
import {FriendPoke} from "@/onebot/action/user/FriendPoke";
|
||||
|
||||
|
||||
export type ActionMap = Map<string, BaseAction<any, any>>;
|
||||
@@ -189,6 +190,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new FetchUserProfileLike(obContext, core),
|
||||
new GetPacketStatus(obContext, core),
|
||||
new GroupPoke(obContext, core),
|
||||
new FriendPoke(obContext, core),
|
||||
new GetUserStatus(obContext, core),
|
||||
new GetRkey(obContext, core),
|
||||
new SetSpecialTittle(obContext, core),
|
||||
|
@@ -9,7 +9,7 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
|
||||
if (!this.core.apis.PacketApi.available) {
|
||||
return {
|
||||
valid: false,
|
||||
message: "PacketClient is not available!",
|
||||
message: "packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置!",
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@@ -17,6 +17,7 @@ export enum ActionName {
|
||||
// 以下为扩展napcat扩展
|
||||
Unknown = 'unknown',
|
||||
GroupPoke = 'group_poke',
|
||||
FriendPoke = 'friend_poke',
|
||||
SharePeer = 'ArkSharePeer',
|
||||
ShareGroupEx = 'ArkShareGroup',
|
||||
RebootNormal = 'reboot_normal',//无快速登录重新启动
|
||||
@@ -126,5 +127,5 @@ export enum ActionName {
|
||||
GetRkey = "nc_get_rkey",
|
||||
SetSpecialTittle = "set_group_special_title",
|
||||
// UploadForwardMsg = "upload_forward_msg",
|
||||
GetGroupShutList = "get_goup_shut_list",
|
||||
GetGroupShutList = "get_group_shut_list",
|
||||
}
|
||||
|
22
src/onebot/action/user/FriendPoke.ts
Normal file
22
src/onebot/action/user/FriendPoke.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user_id: { type: ['number', 'string'] },
|
||||
},
|
||||
required: ['user_id'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class FriendPoke extends GetPacketStatusDepends<Payload, any> {
|
||||
actionName = ActionName.FriendPoke;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
await this.core.apis.PacketApi.sendPokePacket(+payload.user_id);
|
||||
}
|
||||
}
|
@@ -111,7 +111,7 @@ export class OB11Entities {
|
||||
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
|
||||
return {
|
||||
group_id: parseInt(peerId),
|
||||
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileName),
|
||||
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileId ?? ''),
|
||||
file_name: file.fileName,
|
||||
busid: file.busId,
|
||||
size: parseInt(file.fileSize),
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
GroupNotifyMsgStatus,
|
||||
GroupNotifyMsgType,
|
||||
InstanceContext,
|
||||
MsgSourceType,
|
||||
NapCatCore,
|
||||
NodeIKernelBuddyListener,
|
||||
NodeIKernelGroupListener,
|
||||
@@ -303,8 +304,10 @@ export class NapCatOneBot11Adapter {
|
||||
},
|
||||
m.msgId,
|
||||
);
|
||||
await this.emitMsg(m)
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
||||
// if (m.sourceType == MsgSourceType.K_DOWN_SOURCETYPE_AIOINNER) {
|
||||
await this.emitMsg(m)
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
undefined,
|
||||
SettingButton('V3.0.0', 'napcat-update-button', 'secondary'),
|
||||
SettingButton('V3.0.6', 'napcat-update-button', 'secondary'),
|
||||
),
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V3.0.0", "napcat-update-button", "secondary")
|
||||
SettingButton("V3.0.6", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
Reference in New Issue
Block a user