Compare commits

...
61 Commits
Author SHA1 Message Date
手瓜一十雪 fa25d2e779 feat: arm64 29271
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-11-02 13:19:13 +08:00
手瓜一十雪 3ce1c3f0ec feat: support 3.2.13-29271-x64 2024-11-02 10:58:36 +08:00
手瓜一十雪 96dff5141e feat: packet 29271 2024-11-02 10:46:15 +08:00
手瓜一十雪 78d85d9965 feat: 29271 2024-11-02 10:02:39 +08:00
手瓜一十雪 37ec455b02 Merge pull request #484 from NapNeko/feat/ai-voice
feat: ai voice
2024-11-02 08:15:05 +08:00
pk5ls20 6ab82739a6 feat: ai voice 2024-11-02 01:51:57 +08:00
手瓜一十雪 a36917e7c0 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-01 12:13:24 +08:00
手瓜一十雪 21f3428b36 feat: support 6.9.58-28971 2024-11-01 12:13:04 +08:00
Mlikiowa f8a487db25 release: v3.4.5 2024-10-31 12:30:25 +00:00
手瓜一十雪 73a859be04 fix: report self
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-31 20:30:02 +08:00
手瓜一十雪 63bcee01a1 fix: report self 2024-10-31 20:27:17 +08:00
Mlikiowa 85b4966ba8 release: v3.4.4 2024-10-31 11:32:42 +00:00
手瓜一十雪 36c2c567b7 fix: #444
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-31 19:32:10 +08:00
Mlikiowa 7b1ac224f6 release: v3.4.3 2024-10-31 10:18:21 +00:00
手瓜一十雪 34d9f04f15 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-31 18:17:53 +08:00
手瓜一十雪 be5da7cc6f fix: 正向ws异常推送事件问题 2024-10-31 18:17:41 +08:00
Mlikiowa 8d32ccb5d4 release: v3.4.2 2024-10-31 10:03:19 +00:00
手瓜一十雪 6acceb884c fix: #473
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-31 18:00:55 +08:00
Hao Guan 4c834fd640 chore: Major获取Appid添加提示 (#480) 2024-10-31 16:10:27 +08:00
Mlikiowa 301278c7a9 release: v3.4.1 2024-10-31 00:36:50 +00:00
凌莞~(=^▽^=) 42ee83c54f feat: GetStrangerInfo 加回以前的完整信息 (#479)
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-31 07:25:13 +08:00
Mlikiowa e631f69621 release: v3.4.0 2024-10-30 13:11:06 +00:00
Nepenthe ce8760a39a 修复<get_record>接口 (#478)
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-30 21:09:32 +08:00
Mlikiowa ff952956de release: v3.3.27 2024-10-30 07:28:17 +00:00
手瓜一十雪 28f3ff4971 fix: reply msg 大坐牢 #452 #477
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-30 15:27:26 +08:00
手瓜一十雪 19e728c3cb feat: 提升全平台兼容性 2024-10-30 13:50:47 +08:00
Mlikiowa 269773ed6b release: v3.3.26 2024-10-30 01:23:01 +00:00
Hao Guan e0d32417e1 chore: AppID for macOS 6.9.58-28971 (#476)
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-30 09:20:32 +08:00
pk5ls20 9fa6083bed refactor: kill any (#475)
* refactor: kill any stage 1

* refactor: kill any stage 2

* refactor: kill any stage 3
2024-10-30 09:10:30 +08:00
手瓜一十雪 4d2fccdfb4 style: lint 2024-10-29 18:48:20 +08:00
Mlikiowa c1c4bdfe94 release: v3.3.25 2024-10-29 10:42:12 +00:00
手瓜一十雪 8a0e9e8b61 release: v3.3.25
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-29 18:41:39 +08:00
手瓜一十雪 1190e14171 docs: 调整文档优先级 2024-10-29 14:25:36 +08:00
Mlikiowa 00292b177a release: v3.3.22 2024-10-29 02:56:37 +00:00
手瓜一十雪 88de57f984 Merge pull request #472 from pohgxz/main
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
完善<set_input_status>接口
2024-10-29 10:53:29 +08:00
手瓜一十雪 61ddf38892 fix: Error 2024-10-29 10:52:50 +08:00
Nepenthe 52b3540ec3 修改<get_profile_like>接口 2024-10-29 07:51:16 +08:00
Nepenthe 5f831958c3 完善<set_input_status>接口 2024-10-28 23:21:49 +08:00
手瓜一十雪 c3d4698af3 try fix: error 2024-10-28 21:34:13 +08:00
Mlikiowa bd6e83217d release: v3.3.21 2024-10-28 04:05:30 +00:00
pk5ls20 50ec49d9a2 feat: GetMiniAppArk
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-28 10:12:24 +08:00
pk5ls20 dc3a089070 chore: rename msg to message in packet module 2024-10-28 07:59:24 +08:00
Mlikiowa 530e380178 release: v3.3.20 2024-10-27 14:46:24 +00:00
手瓜一十雪 10e4387add fix: script
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:45:51 +08:00
手瓜一十雪 e925bc3aa8 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 22:44:04 +08:00
手瓜一十雪 427b3a7560 release: v3.3.18 2024-10-27 22:43:55 +08:00
Version c8da950725 chore:version change 2024-10-27 14:42:23 +00:00
手瓜一十雪 743c5b8196 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:41:56 +08:00
手瓜一十雪 5e62abea57 fix: version 控制 2024-10-27 22:41:46 +08:00
Version 6bfc545582 chore:version change 2024-10-27 14:37:13 +00:00
手瓜一十雪 411108a2d2 fix: version check
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:36:48 +08:00
Version 308a6fa9e4 chore:version change 2024-10-27 14:33:40 +00:00
Version 2dc7b785d0 chore:version change 2024-10-27 14:33:19 +00:00
手瓜一十雪 0e69e9e839 fix: checkVersion
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:32:52 +08:00
手瓜一十雪 b83229b5da feat: 自动化版本发布控制
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:30:01 +08:00
手瓜一十雪 6f053f5f7d feat: 我补药要手动release啦! 2024-10-27 22:20:11 +08:00
手瓜一十雪 c3dc53eaaf release: v3.3.12
Build Release / check-version (push) Canceled after 0s
Build Release / Build-LiteLoader (push) Canceled after 0s
Build Release / Build-Shell (push) Canceled after 0s
Build Release / release-napcat (push) Canceled after 0s
2024-10-27 22:14:17 +08:00
手瓜一十雪 ffdc34cfe2 Merge pull request #470 from pohgxz/main
修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found>
2024-10-27 22:10:55 +08:00
手瓜一十雪 4825a0e341 fix: type Error 2024-10-27 22:07:11 +08:00
Nepenthe 95a00d7f35 修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found> 2024-10-27 22:04:48 +08:00
手瓜一十雪 d885bab426 feat: 类型修复 2024-10-27 22:03:22 +08:00
64 changed files with 1177 additions and 197 deletions

No files matched your search

+8
View File
@@ -127,6 +127,10 @@ jobs:
zip -q -r NapCat.Framework.Windows.Once.zip *
cd ..
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
mv ./external/packet/napcat.packet.arm64 ./
mv ./external/packet/napcat.packet.exe ./
mv ./external/packet/napcat.packet.linux ./
mv ./external/packet/napcat.packet.production.py ./
- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -143,4 +147,8 @@ jobs:
NapCat.Framework.zip
NapCat.Shell.zip
NapCat.Framework.Windows.Once.zip
napcat.packet.arm64
napcat.packet.exe
napcat.packet.linux
napcat.packet.production.py
draft: true
+1 -1
View File
@@ -25,7 +25,6 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
**首次使用**请务必查看如下文档看使用教程
### 文档地址
[Github.IO](https://napneko.github.io/)
[Cloudflare.Worker](https://doc.napneko.icu/)
@@ -33,6 +32,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Cloudflare.Pages](https://napneko.pages.dev/)
[Github.IO](https://napneko.github.io/)
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+102
View File
@@ -0,0 +1,102 @@
import asyncio #line:1
import datetime #line:2
import json #line:3
import hashlib #line:4
import sys #line:5
import websockets #line:6
import frida #line:7
clients ={}#line:9
event_loop =None #line:10
def generate_md5_hash (O000OO00OOO00O0OO :str )->str :#line:12
return hashlib .md5 (O000OO00OOO00O0OO .encode ()).hexdigest ()#line:13
async def on_message (O000O0000OO0O0000 ,O0O00OO0OOOO000O0 ):#line:15
try :#line:16
if O000O0000OO0O0000 ['type']=='send':#line:17
O000O0O00OO0O0OOO =O000O0000OO0O0000 ['payload']#line:18
if not O0O00OO0OOOO000O0 :#line:19
return #line:20
if O000O0O00OO0O0OOO ['type']=='recv':#line:21
await O0O00OO0OOOO000O0 .send (json .dumps ({"type":"recv","trace_id_md5":O000O0O00OO0O0OOO ['trace_id_md5'],"data":{"seq":O000O0O00OO0O0OOO ['seq'],"cmd":O000O0O00OO0O0OOO ['cmd'],"hex_data":O000O0O00OO0O0OOO ['hex_data']}}))#line:30
elif O000O0O00OO0O0OOO ['type']=='send':#line:31
print ("send data: ",O000O0O00OO0O0OOO )#line:32
await O0O00OO0OOOO000O0 .send (json .dumps ({"type":"send","trace_id_md5":O000O0O00OO0O0OOO ['trace_id_md5'],"data":{}}))#line:37
except Exception as O0O00O0O00O00OO0O :#line:38
print (f"Error in on_message: {O0O00O0O00O00OO0O}")#line:39
print (O000O0000OO0O0000 ['payload'])#line:40
def on_frida_message (OO0O00OOO00000OOO ,OOOOOO0OOO0OOO00O ,O0OO000OOOO00O000 ,O00O000OOO000O0O0 ):#line:42
asyncio .run_coroutine_threadsafe (on_message (OO0O00OOO00000OOO ,O0OO000OOOO00O000 ),O00O000OOO000O0O0 )#line:43
async def initialize_frida (OO000OOO000O0OO00 ,O00OOOO0O0OOOO000 ):#line:45
global clients ,event_loop #line:46
O0O0OOOO00O000O00 =int (OO000OOO000O0OO00 ['pid'])#line:47
O00OOO0O000O0OOO0 =hex (int (OO000OOO000O0OO00 ['recv'],16 )>>1 )#line:48
O0O000OO0O0OOOO00 =hex (int (OO000OOO000O0OO00 ['send'],16 )>>1 )#line:49
print ("init frida with pid: %d"%O0O0OOOO00O000O00 )#line:50
OO0OOO0OOOO000O00 =frida .attach (O0O0OOOO00O000O00 )#line:51
O0OOOOOOO0OO00O00 ="""
const _0x171b=['attach','toString','add','now','recv','event\x20add!','charCodeAt','readPointer','log','set','send','length','startsWith','push','wrapper.node','event\x20send!\x20','findBaseAddress','join','delete','stringify','current_time','sended','seq','readUtf8String','event\x20clear!\x20','trace_id','hook_send\x20napcat!\x20','input','readByteArray'];const _0x5b84=function(_0x171b04,_0x5b84f0){_0x171b04=_0x171b04-0x0;let _0x379e29=_0x171b[_0x171b04];return _0x379e29;};let offset_recv=FRIDA_RECV_OFFSET;let offset_send=FRIDA_SEND_OFFSET;let eventlist=new Map();let isListened=!![];recv(_0x5b84('0x1b'),_0x443b1a=>{isListened=![];if(_0x443b1a&&_0x443b1a[_0x5b84('0x19')]){_0x443b1a['sended']=![];console[_0x5b84('0x8')](_0x5b84('0x5'),JSON['stringify'](_0x443b1a,null,0x2));eventlist[_0x5b84('0x9')](_0x443b1a[_0x5b84('0x19')],_0x443b1a);send({'type':_0x5b84('0xa'),'trace_id_md5':_0x443b1a['trace_id_md5']});}let _0x53dd5f=Date[_0x5b84('0x3')]()/0x3e8;for(let [_0x5981c6,_0x5a5f37]of eventlist){if(_0x5a5f37[_0x5b84('0x14')]+0x3c<_0x53dd5f){console['log'](_0x5b84('0x18'),JSON['stringify'](_0x5a5f37,null,0x2));eventlist[_0x5b84('0x12')](_0x5981c6);}}});function bytesToHex(_0x317040){var _0x475d58=new Uint8Array(_0x317040);for(var _0x247ab6=[],_0x4f61a3=0x0;_0x4f61a3<_0x475d58[_0x5b84('0xb')];_0x4f61a3++){_0x247ab6[_0x5b84('0xd')]((_0x475d58[_0x4f61a3]>>>0x4)['toString'](0x10));_0x247ab6['push']((_0x475d58[_0x4f61a3]&0xf)[_0x5b84('0x1')](0x10));}return _0x247ab6[_0x5b84('0x11')]('');}function HexToBytes(_0x4a226e){var _0x335e51=[];for(var _0x46bc7a=0x0;_0x46bc7a<_0x4a226e['length'];_0x46bc7a+=0x2)_0x335e51[_0x5b84('0xd')](parseInt(_0x4a226e['substr'](_0x46bc7a,0x2),0x10));return _0x335e51;}function String2HexText(_0xba6082){var _0x20ceff=[];for(var _0x28780a=0x0;_0x28780a<_0xba6082['length'];_0x28780a++){_0x20ceff[_0x28780a]=_0xba6082[_0x5b84('0x6')](_0x28780a)['toString'](0x10);}return _0x20ceff[_0x5b84('0x11')]('');}async function main(){let _0x112236=Module[_0x5b84('0x10')]('wrapper.node');while(_0x112236==null){_0x112236=Module[_0x5b84('0x10')](_0x5b84('0xe'));}let _0xce97d=_0x112236['add'](offset_recv);console['log']('hook_recv\x20napcat!\x20');Interceptor[_0x5b84('0x0')](_0xce97d,{'onEnter'(_0x58d330){let _0x366bab=Memory['readPointer'](_0x58d330[0x1])['add'](0x20);let _0x4fc7ff=new Uint8Array(_0x366bab['readByteArray'](0x1))[0x0]&0x1;let _0x28a912=Memory['readPointer'](_0x58d330[0x1])['add'](0x18);let _0x13dd09=Memory[_0x5b84('0x7')](_0x58d330[0x1]);let _0x50360e=new Uint8Array(_0x13dd09[_0x5b84('0x1c')](0x1))[0x0]&0x1;let _0x196dbb=Memory['readPointer'](Memory['readPointer'](_0x58d330[0x1])[_0x5b84('0x2')](0x38));let _0x42cfc8=Memory['readPointer'](_0x196dbb);let _0xca3db8=Memory[_0x5b84('0x7')](_0x196dbb[_0x5b84('0x2')](0x8));let _0x21181a=_0xca3db8-_0x42cfc8;let _0x169a6e=_0x4fc7ff==0x0?Memory[_0x5b84('0x17')](_0x366bab['add'](0x1)):Memory['readUtf8String'](Memory[_0x5b84('0x7')](_0x366bab['add'](0x10)));let _0x3e9755=_0x50360e==0x0?Memory[_0x5b84('0x17')](_0x13dd09['add'](0x1)):Memory[_0x5b84('0x17')](Memory[_0x5b84('0x7')](_0x13dd09[_0x5b84('0x2')](0x10)));let _0x12eb97=Memory['readU32'](_0x28a912);let _0x177b45=bytesToHex(_0x42cfc8['readByteArray'](_0x21181a));let _0xb9a9db='';for(let [_0x7418bd,_0x53b726]of eventlist){if(_0x53b726[_0x5b84('0x16')]==_0x12eb97){_0xb9a9db=_0x53b726['trace_id_md5'];eventlist['delete'](_0x7418bd);break;}}send({'type':_0x5b84('0x4'),'trace_id_md5':_0xb9a9db,'seq':_0x12eb97,'hex_data':_0x177b45,'cmd':_0x169a6e});},'onLeave'(_0x18676d){}});let _0x50e030=_0x112236[_0x5b84('0x2')](offset_send);console['log'](_0x5b84('0x1a'));Interceptor[_0x5b84('0x0')](_0x50e030,{'onEnter'(_0x2c50e5){let _0x43ef68=Memory[_0x5b84('0x7')](_0x2c50e5[0x1])[_0x5b84('0x2')](0x40);let _0x11494e=Memory['readPointer'](_0x2c50e5[0x1])['add'](0x20);let _0x160870=new Uint8Array(_0x11494e['readByteArray'](0x1))[0x0]&0x1;let _0x48c8c0=Memory[_0x5b84('0x7')](Memory['readPointer'](_0x2c50e5[0x1]));let _0x551e15=new Uint8Array(_0x48c8c0['readByteArray'](0x1))[0x0]&0x1;let _0x3d1fb7=Memory[_0x5b84('0x7')](Memory['readPointer'](Memory[_0x5b84('0x7')](_0x2c50e5[0x1]))['add'](0x20));let _0x532d5f=Memory['readPointer'](_0x3d1fb7);let _0xc16bfa=Memory[_0x5b84('0x7')](_0x3d1fb7['add'](0x8));let _0x388601=_0xc16bfa-_0x532d5f;let _0x2f6658=_0x551e15==0x0?Memory['readUtf8String'](_0x48c8c0['add'](0x1)):Memory['readUtf8String'](Memory['readPointer'](_0x48c8c0['add'](0x10)));let _0xa730b0=_0x160870==0x0?Memory[_0x5b84('0x17')](_0x11494e['add'](0x1)):Memory['readUtf8String'](Memory['readPointer'](_0x11494e[_0x5b84('0x2')](0x10)));let _0xd6be27=Memory['readU32'](_0x43ef68);let _0x46ad70=bytesToHex(_0x532d5f[_0x5b84('0x1c')](_0x388601));for(let [_0xecd6b,_0x5390a3]of eventlist){if(_0x46ad70[_0x5b84('0xc')](String2HexText(_0x5390a3[_0x5b84('0x19')]))&&!_0x5390a3['sended']){_0x532d5f['writeByteArray'](HexToBytes(_0x5390a3['hex_data']));_0x5390a3[_0x5b84('0x15')]=!![];_0x5390a3[_0x5b84('0x16')]=_0xd6be27;console[_0x5b84('0xLine truncated
"""#line:55
O0OOOOOOO0OO00O00 =O0OOOOOOO0OO00O00 .replace ("FRIDA_RECV_OFFSET",O00OOO0O000O0OOO0 )#line:56
O0OOOOOOO0OO00O00 =O0OOOOOOO0OO00O00 .replace ("FRIDA_SEND_OFFSET",O0O000OO0O0OOOO00 )#line:57
OOO0000O00O0O0000 =OO0OOO0OOOO000O00 .create_script (O0OOOOOOO0OO00O00 )#line:58
OOO0000O00O0O0000 .on ('message',lambda O00OO0000O0OO000O ,O0O0000OO00OO0OO0 :on_frida_message (O00OO0000O0OO000O ,O0O0000OO00OO0OO0 ,O00OOOO0O0OOOO000 ,event_loop ))#line:59
OOO0000O00O0O0000 .load ()#line:60
clients [O00OOOO0O0OOOO000 ]={'pid':O0O0OOOO00O000O00 ,'frida_script':OOO0000O00O0O0000 ,'session':OO0OOO0OOOO000O00 }#line:66
await O00OOOO0O0OOOO000 .send (json .dumps ({"type":"init",'trace_id':'init',"data":{}}))#line:68
async def handle_send (OO0O00000O0000O00 ,O0O0000OOOOO00000 ):#line:70
global clients #line:71
OO0O00OO0OO0OOO0O =OO0O00000O0000O00 ['data']#line:72
OOOO00000O00OOO00 =OO0O00000O0000O00 ['trace_id']#line:73
O000O0000000O00O0 =OO0O00000O0000O00 ['cmd']#line:74
O00OOOO0OOOOO0O0O =int (datetime .datetime .now ().timestamp ())#line:75
O00OOO00000O000OO =clients [O0O0000OOOOO00000 ]['frida_script']#line:76
O00OOO00000O000OO .post ({'type':'input','cmd':O000O0000000O00O0 ,'hex_data':OO0O00OO0OO0OOO0O ,'trace_id':OOOO00000O00OOO00 ,'current_time':O00OOOO0OOOOO0O0O ,'trace_id_md5':generate_md5_hash (OOOO00000O00OOO00 )})#line:84
async def websocket_handler (O000OO0OO0OO0OO0O ,OO0O0OO0O00OOOO0O ):#line:86
global clients #line:87
try :#line:88
async for O0OOOOO0O00O0OO00 in O000OO0OO0OO0OO0O :#line:89
O00O0O0O000OO0O0O =json .loads (O0OOOOO0O00O0OO00 )#line:90
O000O0000O00OO00O =O00O0O0O000OO0O0O .get ('action')#line:91
if O000O0000O00OO00O =='init':#line:92
O0OO0000OOOOO000O =False #line:93
for OO00OO000O00O0OOO in frida .get_local_device ().enumerate_processes ():#line:94
if OO00OO000O00O0OOO .pid ==clients .get (O000OO0OO0OO0OO0O ,{}).get ('pid'):#line:95
O0OO0000OOOOO000O =True #line:96
if not O0OO0000OOOOO000O :#line:97
await initialize_frida (O00O0O0O000OO0O0O ,O000OO0OO0OO0OO0O )#line:98
elif O000O0000O00OO00O =='send'and O000OO0OO0OO0OO0O in clients :#line:99
await handle_send (O00O0O0O000OO0O0O ,O000OO0OO0OO0OO0O )#line:100
except Exception as O0OOOO0O00O000O0O :#line:101
print (f"WebSocket connection closed: {O0OOOO0O00O000O0O}")#line:102
finally :#line:103
if O000OO0OO0OO0OO0O in clients :#line:104
clients [O000OO0OO0OO0OO0O ]['session'].detach ()#line:105
del clients [O000OO0OO0OO0OO0O ]#line:106
async def main ():#line:108
global event_loop #line:109
event_loop =asyncio .get_running_loop ()#line:110
OOOOOO0OOO0000O0O =None #line:111
OOO000OO0O0OOOO00 =None #line:112
if '-ip'in sys .argv and '-port'in sys .argv :#line:114
O00000OOO00OO000O =sys .argv .index ('-ip')+1 #line:115
OOO0O00O0OO0000OO =sys .argv .index ('-port')+1 #line:116
if O00000OOO00OO000O <len (sys .argv )and OOO0O00O0OO0000OO <len (sys .argv ):#line:117
OOOOOO0OOO0000O0O =sys .argv [O00000OOO00OO000O ]#line:118
OOO000OO0O0OOOO00 =int (sys .argv [OOO0O00O0OO0000OO ])#line:119
if OOOOOO0OOO0000O0O is None or OOO000OO0O0OOOO00 is None :#line:121
O0OOOOOO0OO0O0OO0 =read_config ()#line:122
OOOOOO0OOO0000O0O =O0OOOOOO0OO0O0OO0 ['ip']#line:123
OOO000OO0O0OOOO00 =O0OOOOOO0OO0O0OO0 ['port']#line:124
print ("listen...",OOOOOO0OOO0000O0O ,OOO000OO0O0OOOO00 )#line:126
async with websockets .serve (websocket_handler ,OOOOOO0OOO0000O0O ,OOO000OO0O0OOOO00 ):#line:127
await asyncio .get_running_loop ().create_future ()#line:128
def read_config ():#line:130
try :#line:131
with open ("config.json","r")as OO0O0O00OOOOO0OO0 :#line:132
O0O00O0OO00O00000 =json .load (OO0O0O00OOOOO0OO0 )#line:133
except FileNotFoundError :#line:134
O0O00O0OO00O00000 ={"ip":"127.0.0.1","port":8086 }#line:135
with open ("config.json","w")as OO0O0O00OOOOO0OO0 :#line:136
OO0O0O00OOOOO0OO0 .write (json .dumps (O0O00O0OO00O00000 ))#line:137
return O0O00O0OO00O00000 #line:138
if __name__ =='__main__':#line:140
asyncio .run (main ())
+1 -1
View File
@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "3.2.12",
"version": "3.4.5",
"icon": "./logo.png",
"authors": [
{
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "3.2.12",
"version": "3.4.5",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
+19 -5
View File
@@ -4,16 +4,27 @@ const process = require("process");
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
try {
const packageJson = require("../package.json");
const manifsetJson = require("../manifest.json");
const currentVersion = packageJson.version;
const targetVersion = process.env.VERSION;
const manifestCurrentVersion = manifsetJson.version;
const manifestTargetVersion = process.env.VERSION;
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
console.log("[NapCat] [CheckVersion] manifestCurrentVersion:", manifestCurrentVersion, "manifestTargetVersion:", manifestTargetVersion);
// 验证 targetVersion 格式
if (!targetVersion || typeof targetVersion !== 'string') {
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
return;
}
// 验证 manifestTargetVersion 格式
if (!manifestTargetVersion || typeof manifestTargetVersion !== 'string') {
console.log("[NapCat] [CheckVersion] manifest目标版本格式不正确或未设置!");
return;
}
// 写入脚本文件的统一函数
const writeScriptToFile = (content) => {
@@ -21,7 +32,7 @@ try {
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
};
if (currentVersion === targetVersion) {
if (currentVersion === targetVersion && manifestCurrentVersion === manifestTargetVersion) {
// 不需要更新版本,写入一个简单的脚本
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
writeScriptToFile(simpleScript);
@@ -29,11 +40,14 @@ try {
// 更新版本,构建安全的sed命令
const safeScriptContent = `
#!/bin/bash
git config --global user.email "bot@test.wumiao.wang"
git config --global user.name "Version"
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
git config --global user.email "nanaeonn@outlook.com"
git config --global user.name "Mlikiowa"
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
sed -i "s/SettingButton(\\"V.*\\", \\"napcat-update-button\\", \\"secondary\\")/SettingButton(\\"V${targetVersion}\\", \\"napcat-update-button\\", \\"secondary\\")/g" ./static/assets/renderer.js
git add .
git commit -m "chore:version change"
git commit -m "release: v${targetVersion}"
git push -u origin main`;
writeScriptToFile(safeScriptContent);
}
+19 -20
View File
@@ -9,6 +9,15 @@ interface InternalMapKey {
checker: ((...args: any[]) => boolean) | undefined;
}
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
type FuncKeys<T> = Extract<
{
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
}[keyof T],
string
>;
export type ListenerClassBase = Record<string, string>;
export class NTEventWrapper {
@@ -43,10 +52,8 @@ export class NTEventWrapper {
createEventFunction<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/');
type eventType = {
@@ -98,10 +105,8 @@ export class NTEventWrapper {
async callNoListenerEvent<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType>
@@ -111,10 +116,8 @@ export class NTEventWrapper {
async registerListen<
Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
>(
listenerAndMethod: `${Listener}/${ListenerMethod}`,
waitTimes = 1,
@@ -164,15 +167,11 @@ export class NTEventWrapper {
async callNormalEventV2<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
// eslint-disable-next-line
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`,
+1 -1
View File
@@ -1,4 +1,4 @@
import { PacketMsg } from "@/core/packet/msg/message";
import { PacketMsg } from "@/core/packet/message/message";
import * as crypto from "node:crypto";
interface ForwardMsgJson {
+35 -2
View File
@@ -52,7 +52,7 @@ export class FileNapCatOneBotUUID {
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
return {
peer: {
chatType: chatType as any,
chatType: +chatType,
peerUid: peerUid,
},
modelId,
@@ -89,7 +89,7 @@ export class FileNapCatOneBotUUID {
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
return {
peer: {
chatType: chatType as any,
chatType: +chatType,
peerUid: peerUid,
},
msgId,
@@ -245,3 +245,36 @@ export function stringifyWithBigInt(obj: any) {
typeof value === 'bigint' ? value.toString() : value
);
}
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
const hexSequence = "A4 09 00 00 00 35";
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
const filePath = path.resolve(nodeMajor);
const fileContent = fs.readFileSync(filePath);
let searchPosition = 0;
while (true) {
const index = fileContent.indexOf(sequenceBytes, searchPosition);
if (index === -1) {
break;
}
const start = index + sequenceBytes.length - 1;
const end = fileContent.indexOf(0x00, start);
if (end === -1) {
break;
}
const content = fileContent.subarray(start, end);
if (!content.every(byte => byte === 0x00)) {
try {
return content.toString("utf-8");
} catch (error) {
break;
}
}
searchPosition = end + 1;
}
return undefined;
}
+4 -4
View File
@@ -23,10 +23,10 @@ export class LimitedHashTable<K, V> {
}
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
const oldestKey = this.keyToValue.keys().next().value;
// @ts-ignore
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
// @ts-ignore
this.keyToValue.delete(oldestKey);
if (oldestKey !== undefined) {
this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
this.keyToValue.delete(oldestKey);
}
}
}
+20 -3
View File
@@ -1,8 +1,9 @@
import fs from 'node:fs';
import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
import { getMajorPath } from '@/core';
export class QQBasicInfoWrapper {
QQMainPath: string | undefined;
@@ -72,6 +73,7 @@ export class QQBasicInfoWrapper {
}
getAppidV2(): { appid: string; qua: string } {
// 通过已有表 性能好
const appidTbale = AppidTable as unknown as QQAppidTableType;
const fullVersion = this.getFullQQVesion();
if (fullVersion) {
@@ -80,10 +82,25 @@ export class QQBasicInfoWrapper {
return data;
}
}
// else
// 通过Major拉取 性能差
try {
let majorAppid = this.getAppidV2ByMajor(fullVersion);
if (majorAppid) {
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
return { appid: majorAppid, qua: this.getQUAFallback() };
}
} catch (error) {
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 最终兜底为老版本
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
}
getAppidV2ByMajor(QQVersion: string) {
let majorPath = getMajorPath(QQVersion);
let appid = parseAppidFromMajor(majorPath);
return appid;
}
}
+1 -1
View File
@@ -1 +1 @@
export const napCatVersion = '3.2.12';
export const napCatVersion = '3.4.5';
+46 -10
View File
@@ -54,7 +54,9 @@ export class NTQQGroupApi {
}, pskey);
}
async getGroupShutUpMemberList(groupCode: string) {
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
let data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
return (await data)[1];
}
async clearGroupNotifiesUnreadCount(uk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
@@ -320,10 +322,10 @@ export class NTQQGroupApi {
infos: Map<string, GroupMember>;
finish: boolean;
hasNext: boolean | undefined;
}>{
}> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
.catch(() => {});
.catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
@@ -337,21 +339,55 @@ export class NTQQGroupApi {
}
this.context.session.getGroupService().destroyMemberListScene(sceneId);
return {
infos: resMode2?.infos || result.result.infos,
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish,
hasNext: resMode2?.hasNext,
};
}
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
infos: Map<string, GroupMember>;
finish: boolean;
hasNext: boolean | undefined;
listenerMode: boolean;
}> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
.catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
}
let resMode2;
if (result.result.finish && result.result.infos.size === 0) {
const ret = (await once)?.[0];
if (ret) {
resMode2 = ret;
}
}
this.context.session.getGroupService().destroyMemberListScene(sceneId);
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
return {
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish,
hasNext: resMode2?.hasNext,
listenerMode: resMode2?.hasNext !== undefined ? true : false
};
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
let res = await this.tryGetGroupMembersV2(true, groupQQ);
if (res.hasNext || !res.finish || res.infos.size === 0) {
res = await this.tryGetGroupMembersV2(false, groupQQ, num);
//console.log('getGroupMembers -->', groupQQ);
let res = await this.GetGroupMembersV3(groupQQ, num);
let ret = res.infos;
if (res.infos.size === 0 && !res.listenerMode) {
res = await this.GetGroupMembersV3(groupQQ, num);
ret = res.infos;
}
if ((res.infos.size === 0 || res.infos.size === 30) && res.finish) {
res = await this.tryGetGroupMembersV2(true, groupQQ, num);
if (res.infos.size === 0) {
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
}
return res.infos;
//console.log("<---------------")
return ret;
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
+9 -4
View File
@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
}
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
@@ -106,9 +111,9 @@ export class NTQQMsgApi {
pageLimit: 1,
});
}
//@deprecated
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
// 客户端还在用别慌
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
}
async getMsgExBySeq(peer: Peer, msgSeq: string) {
const DateNow = Math.floor(Date.now() / 1000);
+61 -3
View File
@@ -1,23 +1,30 @@
import * as crypto from 'crypto';
import * as os from 'os';
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 { OidbPacket, PacketHexStr } from "@/core/packet/packer";
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
import { NapProtoEncodeStructType, 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 { PacketMsg } from "@/core/packet/message/message";
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
import {
PacketMsgFileElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from "@/core/packet/msg/element";
} from "@/core/packet/message/element";
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat";
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
interface OffsetType {
@@ -185,4 +192,55 @@ export class NTQQPacketApi {
}
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
}
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
const info = resp.download.info;
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
}
async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
return JSON.parse(body.content.jsonContent) as MiniAppRawData;
}
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
if (!resp.content) return null;
return resp.content.map((item) => {
return {
category: item.category,
voices: item.voices
};
});
}
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoEncodeStructType<typeof MsgInfo>> {
let reqTime = 0;
const reqMaxTime = 30;
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
while (true) {
if (reqTime >= reqMaxTime) {
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
}
reqTime++;
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
if (body.errorCode) {
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
}
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
if (!resp.msgInfo) continue;
return resp.msgInfo;
}
}
}
+3 -3
View File
@@ -18,7 +18,7 @@ export class NTQQUserApi {
async getStatusByUid(uid: string) {
return this.context.session.getProfileService().getStatus(uid);
}
async getProfileLike(uid: string) {
async getProfileLike(uid: string, start: number, count: number) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid],
basic: 1,
@@ -26,8 +26,8 @@ export class NTQQUserApi {
favorite: 0,
userProfile: 1,
type: 2,
start: 0,
limit: 20,
start: start,
limit: count,
});
}
async fetchOtherProfileLike(uid: string) {
+3 -1
View File
@@ -64,7 +64,7 @@ type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBu
type ElementBase<
K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = {}
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = {
[P in K]:
S[P] extends Array<infer U>
@@ -822,6 +822,8 @@ export interface RawMessage {
elements: MessageElement[];
sourceType: MsgSourceType;
isOnlineMsg: boolean;
}
export interface QueryMsgsParams {
chatInfo: Peer;
+44
View File
@@ -43,6 +43,50 @@ export enum GroupInviteType {
BYGROUPMEMBER,
BYDISCUSSMEMBER
}
export interface ShutUpGroupHonor {
[key: string]: number;
}
export interface ShutUpGroupMember {
uid: string;
qid: string;
uin: string;
nick: string;
remark: string;
cardType: number;
cardName: string;
role: number;
avatarPath: string;
shutUpTime: number;
isDelete: boolean;
isSpecialConcerned: boolean;
isSpecialShield: boolean;
isRobot: boolean;
groupHonor: ShutUpGroupHonor;
memberRealLevel: number;
memberLevel: number;
globalGroupLevel: number;
globalGroupPoint: number;
memberTitleId: number;
memberSpecialTitle: string;
specialTitleExpireTime: string;
userShowFlag: number;
userShowFlagNew: number;
richFlag: number;
mssVipType: number;
bigClubLevel: number;
bigClubFlag: number;
autoRemark: string;
creditLevel: number;
joinTime: number;
lastSpeakTime: number;
memberFlag: number;
memberFlagExt: number;
memberMobileFlag: number;
memberFlagExt2: number;
isSpecialShielded: boolean;
cardNameId: number;
}
export interface GroupNotify {
seq: string; // 通知序列号
+17 -1
View File
@@ -51,12 +51,28 @@
"appid": 537249739,
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
},
"9.9.16-28971":{
"9.9.16-28971": {
"appid": 537249775,
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
},
"3.2.13-28971": {
"appid": 537249848,
"qua": "V1_LNX_NQ_3.2.13_28971_GW_B"
},
"6.9.58-28971": {
"appid": 537249826,
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
},
"9.9.16-29271": {
"appid": 537249813,
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
},
"3.2.13-29271": {
"appid": 537249913,
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
},
"6.9.59-29271": {
"appid": 537249863,
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
}
}
+19 -3
View File
@@ -1,4 +1,8 @@
{
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
},
"3.2.12-28418-x64": {
"recv": "A0723E0",
"send": "A06EAE0"
@@ -35,8 +39,20 @@
"send": "6E91318",
"recv": "6E94B50"
},
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
"6.9.58-28971-arm64": {
"send": "449ACA0",
"recv": "449D50C"
},
"9.9.16-29271-x64": {
"send": "3833510",
"recv": "3837944"
},
"3.2.13-29271-x64": {
"send": "A11E680",
"recv": "A121F80"
},
"3.2.13-29271-arm64": {
"send": "6ECA098",
"recv": "6ECD8D0"
}
}
+24 -5
View File
@@ -62,7 +62,26 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export function getMajorPath(QQVersion: string): string {
// major.node
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else if (os.platform() === 'linux') {
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
} else {
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
}
let majorPath = path.resolve(appPath, 'major.node');
if (!fs.existsSync(majorPath)) {
majorPath = path.join(appPath, `./resources/app/major.node`);
}
//老版本兼容 未来去掉
if (!fs.existsSync(majorPath)) {
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
}
return majorPath;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: StableNTApiWrapper;
@@ -100,7 +119,7 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
this.context.logger.setFileLogEnabled(
@@ -140,7 +159,7 @@ export class NapCatCore {
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
proxiedListenerOf(msgListener, this.context.logger),
);
const profileListener = new NodeIKernelProfileListener();
@@ -236,7 +255,7 @@ export class NapCatCore {
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any,
proxiedListenerOf(groupListener, this.context.logger),
);
}
@@ -276,7 +295,7 @@ export async function genSessionConfig(
d2: '',
d2Key: '',
machineId: '',
platform: systemPlatform, // 3是Windows?
platform: systemPlatform, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: QQVersionAppid,
rdeliveryConfig: {
@@ -1,4 +1,4 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { }
@@ -80,6 +80,6 @@ export class NodeIKernelGroupListener {
onSearchMemberChange(...args: unknown[]) {
}
onShutUpMemberListChanged(...args: unknown[]) {
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>) {
}
}
+16
View File
@@ -0,0 +1,16 @@
export enum AIVoiceChatType {
Unknown = 0,
Sound = 1,
Sing = 2
}
export interface AIVoiceItem {
voiceId: string;
voiceDisplayName: string;
voiceExampleUrl: string;
}
export interface AIVoiceItemList {
category: string;
voices: AIVoiceItem[];
}
+79
View File
@@ -0,0 +1,79 @@
export interface MiniAppReqCustomParams {
title: string;
desc: string;
picUrl: string;
jumpUrl: string;
}
export interface MiniAppReqTemplateParams {
sdkId: string;
appId: string;
scene: number;
iconUrl: string;
templateType: number;
businessType: number;
verType: number;
shareType: number;
versionId: string;
withShareTicket: number;
}
export interface MiniAppReqParams extends MiniAppReqCustomParams, MiniAppReqTemplateParams {}
export interface MiniAppData {
ver: string;
prompt: string;
config: Config;
app: string;
view: string;
meta: MetaData;
miniappShareOrigin: number;
miniappOpenRefer: string;
}
export interface MiniAppRawData {
appName: string;
appView: string;
ver: string;
desc: string;
prompt: string;
metaData: MetaData;
config: Config;
}
interface Config {
type: string;
width: number;
height: number;
forward: number;
autoSize: number;
ctime: number;
token: string;
}
interface Host {
uin: number;
nick: string;
}
interface Detail {
appid: string;
appType: number;
title: string;
desc: string;
icon: string;
preview: string;
url: string;
scene: number;
host: Host;
shareTemplateId: string;
shareTemplateData: Record<string, unknown>;
showLittleTail: string;
gamePoints: string;
gamePointsUrl: string;
shareOrigin: number;
}
interface MetaData {
detail_1: Detail;
}
+94
View File
@@ -0,0 +1,94 @@
import {
MiniAppData,
MiniAppReqParams,
MiniAppRawData,
MiniAppReqCustomParams,
MiniAppReqTemplateParams
} from "@/core/packet/entities/miniApp";
type MiniAppTemplateNameList = "bili" | "weibo";
export abstract class MiniAppInfo {
static sdkId: string = "V1_PC_MINISDK_99.99.99_1_APP_A";
template: MiniAppReqTemplateParams;
private static appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
protected constructor(template: MiniAppReqTemplateParams) {
this.template = template;
}
static get(name: MiniAppTemplateNameList): MiniAppInfo | undefined {
return this.appMap.get(name);
}
static Bili = new class extends MiniAppInfo {
constructor() {
super({
sdkId: MiniAppInfo.sdkId,
appId: "1109937557",
scene: 1,
templateType: 1,
businessType: 0,
verType: 3,
shareType: 0,
versionId: "cfc5f7b05b44b5956502edaecf9d2240",
withShareTicket: 0,
iconUrl: "https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg"
});
MiniAppInfo.appMap.set("bili", this);
}
};
static WeiBo = new class extends MiniAppInfo {
constructor() {
super({
sdkId: MiniAppInfo.sdkId,
appId: "1109224783",
scene: 1,
templateType: 1,
businessType: 0,
verType: 3,
shareType: 0,
versionId: "e482a3cc4e574d9b772e96ba6eec9ba2",
withShareTicket: 0,
iconUrl: "https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg"
});
MiniAppInfo.appMap.set("weibo", this);
}
};
}
export class MiniAppInfoHelper {
static generateReq(custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams {
return {
...custom,
...template
};
}
static RawToSend(rawData: MiniAppRawData): MiniAppData {
return {
ver: rawData.ver,
prompt: rawData.prompt,
config: rawData.config,
app: rawData.appName,
view: rawData.appView,
meta: rawData.metaData,
miniappShareOrigin: 3,
miniappOpenRefer: "10002",
};
}
static SendToRaw(data: MiniAppData): MiniAppRawData {
return {
appName: data.app,
appView: data.view,
ver: data.ver,
desc: data.meta.detail_1.desc,
prompt: data.prompt,
metaData: data.meta,
config: data.config,
};
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import {
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from "@/core/packet/msg/element";
} from "@/core/packet/message/element";
import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
@@ -2,8 +2,8 @@ import * as crypto from "crypto";
import { PushMsgBody } from "@/core/packet/proto/message/message";
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
import { LogWrapper } from "@/common/log";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/msg/element";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
import { SendTextElement } from "@/core";
export class PacketMsgBuilder {
@@ -30,8 +30,8 @@ import {
PacketMsgTextElement,
PacketMsgVideoElement,
PacketMultiMsgElement
} from "@/core/packet/msg/element";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
} from "@/core/packet/message/element";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
import { LogWrapper } from "@/common/log";
const SupportedElementTypes = [
@@ -25,7 +25,7 @@ import {
SendVideoElement
} from "@/core";
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component";
import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
@@ -1,4 +1,4 @@
import { IPacketMsgElement } from "@/core/packet/msg/element";
import { IPacketMsgElement } from "@/core/packet/message/element";
import { SendMessageElement, SendStructLongMsgElement } from "@/core";
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
+101 -6
View File
@@ -1,31 +1,35 @@
import * as zlib from "node:zlib";
import * as crypto from "node:crypto";
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1";
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { IndexNode, NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
import { PacketMsgBuilder } from "@/core/packet/msg/builder";
import { PacketMsgBuilder } from "@/core/packet/message/builder";
import {
PacketMsgFileElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from "@/core/packet/msg/element";
} from "@/core/packet/message/element";
import { LogWrapper } from "@/common/log";
import { PacketMsg } from "@/core/packet/msg/message";
import { PacketMsg } from "@/core/packet/message/message";
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
import { PacketMsgConverter } from "@/core/packet/msg/converter";
import { PacketMsgConverter } from "@/core/packet/message/converter";
import { PacketClient } from "@/core/packet/client";
import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700";
import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0} from "@/core/packet/proto/oidb/Oidb.0x929";
export type PacketHexStr = string & { readonly hexNya: unique symbol };
@@ -694,6 +698,37 @@ export class PacketPacker {
);
}
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 4,
command: 200
},
scene: {
requestType: 1,
businessType: 3,
sceneType: 2,
group: {
groupUin: groupUin
}
},
client: {
agentType: 2
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0,
}
}
}
}), true, false);
}
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
{
@@ -705,4 +740,64 @@ export class PacketPacker {
}
), false, false);
}
packMiniAppAdaptShareInfo(req: MiniAppReqParams): PacketHexStr {
return this.packetPacket(
new NapProtoMsg(MiniAppAdaptShareInfoReq).encode(
{
appId: req.sdkId,
body: {
extInfo: {
field2: Buffer.alloc(0)
},
appid: req.appId,
title: req.title,
desc: req.desc,
time: BigInt(Date.now()),
scene: req.scene,
templateType: req.templateType,
businessType: req.businessType,
picUrl: req.picUrl,
vidUrl: "",
jumpUrl: req.jumpUrl,
iconUrl: req.iconUrl,
verType: req.verType,
shareType: req.shareType,
versionId: req.versionId,
withShareTicket: req.withShareTicket,
webURL: "",
appidRich: Buffer.alloc(0),
template: {
templateId: "",
templateData: ""
},
field20: ""
}
}
)
);
}
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
return this.packOidbPacket(0x929D, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
groupUin: groupUin,
chatType: chatType
})
);
}
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
return this.packOidbPacket(0x929B, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
groupUin: groupUin,
voiceId: voiceId,
text: text,
chatType: chatType,
session: {
sessionId: sessionId
}
})
);
}
}
+8 -8
View File
@@ -61,35 +61,35 @@ export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageTyp
}
}
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
type ProtoFieldReturnType<T, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
? ScalarTypeToTsType<S>
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
: never;
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = {
type RequiredFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = {
type OptionalFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type RequiredFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
type RequiredFieldsType<T, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
type OptionalFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
type OptionalFieldsType<T, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
type NapProtoStructType<T extends unknown, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>;
export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>;
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
@@ -0,0 +1,49 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const MiniAppAdaptShareInfoReq = {
appId: ProtoField(2, ScalarType.STRING),
body: ProtoField(4, () => MiniAppAdaptShareInfoReqBody),
};
export const MiniAppAdaptShareInfoReqBody = {
extInfo: ProtoField(1, () => ExtInfo),
appid: ProtoField(2, ScalarType.STRING),
title: ProtoField(3, ScalarType.STRING),
desc: ProtoField(4, ScalarType.STRING),
time: ProtoField(5, ScalarType.UINT64),
scene: ProtoField(6, ScalarType.UINT32),
templateType: ProtoField(7, ScalarType.UINT32),
businessType: ProtoField(8, ScalarType.UINT32),
picUrl: ProtoField(9, ScalarType.STRING),
vidUrl: ProtoField(10, ScalarType.STRING),
jumpUrl: ProtoField(11, ScalarType.STRING),
iconUrl: ProtoField(12, ScalarType.STRING),
verType: ProtoField(13, ScalarType.UINT32),
shareType: ProtoField(14, ScalarType.UINT32),
versionId: ProtoField(15, ScalarType.STRING),
withShareTicket: ProtoField(16, ScalarType.UINT32),
webURL: ProtoField(17, ScalarType.STRING),
appidRich: ProtoField(18, ScalarType.BYTES),
template: ProtoField(19, () => Template),
field20: ProtoField(20, ScalarType.STRING),
};
export const ExtInfo = {
field2: ProtoField(2, ScalarType.BYTES),
};
export const Template = {
templateId: ProtoField(1, ScalarType.STRING),
templateData: ProtoField(2, ScalarType.STRING),
};
export const MiniAppAdaptShareInfoResp = {
field2: ProtoField(2, ScalarType.UINT32),
field3: ProtoField(3, ScalarType.STRING),
content: ProtoField(4, () => MiniAppAdaptShareInfoRespContent),
};
export const MiniAppAdaptShareInfoRespContent = {
jsonContent: ProtoField(2, ScalarType.STRING),
};
+42
View File
@@ -0,0 +1,42 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const OidbSvcTrpcTcp0X929D_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
chatType: ProtoField(2, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929D_0Resp = {
content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContent = {
category: ProtoField(1, ScalarType.STRING),
voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContentVoice = {
voiceId: ProtoField(1, ScalarType.STRING),
voiceDisplayName: ProtoField(2, ScalarType.STRING),
voiceExampleUrl: ProtoField(3, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0X929B_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
voiceId: ProtoField(2, ScalarType.STRING),
text: ProtoField(3, ScalarType.STRING),
chatType: ProtoField(4, ScalarType.UINT32),
session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session),
};
export const OidbSvcTrpcTcp0X929B_0_Session = {
sessionId: ProtoField(1, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929B_0Resp = {
statusCode: ProtoField(1, ScalarType.UINT32),
field2: ProtoField(2, ScalarType.UINT32, true),
field3: ProtoField(3, ScalarType.UINT32),
msgInfo: ProtoField(4, () => MsgInfo, true),
};
+2 -1
View File
@@ -4,10 +4,11 @@ import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcpBase = {
command: ProtoField(1, ScalarType.UINT32),
subCommand: ProtoField(2, ScalarType.UINT32),
errorCode: ProtoField(3, ScalarType.UINT32),
body: ProtoField(4, ScalarType.BYTES),
errorMsg: ProtoField(5, ScalarType.STRING, true),
isReserved: ProtoField(12, ScalarType.UINT32)
};
export const OidbSvcTrpcTcpBaseRsp = {
body: ProtoField(4, ScalarType.BYTES)
};
};
+6 -5
View File
@@ -12,13 +12,13 @@ import {
import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelGroupService {
// --->
// --->
// 待启用 For Next Version 3.2.0
// isTroopMember ? 0 : 111
getGroupMemberMaxNum(groupCode: string, serviceType: number): Promise<unknown>;
getAllGroupPrivilegeFlag(troopUinList: string[], serviceType: number): Promise<unknown>;
// <---
// <---
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
@@ -105,13 +105,13 @@ export interface NodeIKernelGroupService {
uid: string,
index: number//0
}>,
infos: unknown,
infos: Map<string, GroupMember>,
finish: true,
hasRobot: false
}
}>;
setHeader(uid: string, path: string): unknown;
setHeader(uid: string, path: string): Promise<GeneralCallResult>;
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
@@ -232,7 +232,8 @@ export interface NodeIKernelGroupService {
getGroupStatisticInfo(groupCode: string): unknown;
getGroupRemainAtTimes(groupCode: string): Promise<GeneralCallResult & {
getGroupRemainAtTimes(groupCode: string): Promise<Omit<GeneralCallResult, 'result'> & {
errCode: number,
atInfo: {
canAtAll: boolean
RemainAtAllCountForUin: number
+11 -9
View File
@@ -172,7 +172,7 @@ export interface NodeIKernelMsgService {
msgList: RawMessage[]
}>;
//@deprecated
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
@@ -186,27 +186,29 @@ export interface NodeIKernelMsgService {
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
// 下面的msgid全部不真实
getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown;
//用法和聊天记录一样
getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
type: number,
subtype: Array<number>
}): unknown;
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
type: number,
subtype: Array<number>
}>): unknown;
}>): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown;
getMsgWithAbstractByFilterParam(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
queryMsgsWithFilter(...args: unknown[]): unknown;
queryMsgsWithFilter(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
+1 -1
View File
@@ -41,7 +41,7 @@ export async function NCoreInitFramework(
online: true,
});
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
});
// 过早进入会导致addKernelMsgListener等Listener添加失败
// await sleep(2500);
@@ -0,0 +1,41 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
chat_type: { type: ['number', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
interface GetAiCharactersResponse {
type: string;
characters: {
character_id: string;
character_name: string;
preview_url: string;
}[];
}
export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharactersResponse[]> {
actionName = ActionName.GetAiCharacters;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawList = await this.core.apis.PacketApi.sendFetchAiVoiceListReq(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
return rawList?.map((item) => ({
type: item.category,
characters: item.voices.map((voice) => ({
character_id: voice.voiceId,
character_name: voice.voiceDisplayName,
preview_url: voice.voiceExampleUrl,
})),
})) ?? [];
}
}
@@ -0,0 +1,85 @@
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/helper/miniAppHelper";
const SchemaData = {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['bili', 'weibo']
},
title: { type: 'string' },
desc: { type: 'string' },
picUrl: { type: 'string' },
jumpUrl: { type: 'string' },
iconUrl: { type: 'string' },
sdkId: { type: 'string' },
appId: { type: 'string' },
scene: { type: ['number', 'string'] },
templateType: { type: ['number', 'string'] },
businessType: { type: ['number', 'string'] },
verType: { type: ['number', 'string'] },
shareType: { type: ['number', 'string'] },
versionId: { type: 'string' },
withShareTicket: { type: ['number', 'string'] },
rawArkData: { type: ['boolean', 'string'] }
},
oneOf: [
{
required: ['type', 'title', 'desc', 'picUrl', 'jumpUrl']
},
{
required: [
'title', 'desc', 'picUrl', 'jumpUrl',
'iconUrl', 'appId', 'scene', 'templateType', 'businessType',
'verType', 'shareType', 'versionId', 'withShareTicket'
]
}
]
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
data: MiniAppData | MiniAppRawData
}> {
actionName = ActionName.GetMiniAppArk;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
let reqParam: MiniAppReqParams;
const customParams = {
title: payload.title,
desc: payload.desc,
picUrl: payload.picUrl,
jumpUrl: payload.jumpUrl
} as MiniAppReqCustomParams;
if (payload.type) {
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
} else {
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload as Required<Payload>;
reqParam = MiniAppInfoHelper.generateReq(
customParams,
{
sdkId: payload.sdkId ?? MiniAppInfo.sdkId,
appId: appId,
scene: +scene,
iconUrl: iconUrl,
templateType: +templateType,
businessType: +businessType,
verType: +verType,
shareType: +shareType,
versionId: versionId,
withShareTicket: +withShareTicket
}
);
}
const arkData = await this.core.apis.PacketApi.sendMiniAppShareInfoReq(reqParam);
return {
data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
};
}
}
+13 -6
View File
@@ -1,15 +1,22 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ActionName, BaseCheckResult } from '../types';
export class GetProfileLike extends BaseAction<void, any> {
interface Payload {
start: number,
count: number
}
export class GetProfileLike extends BaseAction<Payload, any> {
actionName = ActionName.GetProfileLike;
async _handle(payload: void) {
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid);
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
async _handle(payload: Payload) {
const start = payload.start ? Number(payload.start) : 0;
const count = payload.count ? Number(payload.count) : 10;
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
const listdata: any[] = ret.info.userLikeInfos[0].voteInfo.userInfos;
for (let i = 0; i < listdata.length; i++) {
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
}
return listdata;
return ret.info.userLikeInfos[0].voteInfo;
}
}
+11 -23
View File
@@ -1,16 +1,15 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ActionName, BaseCheckResult } from '../types';
import { ChatType, Peer } from '@/core';
const SchemaData = {
type: 'object',
properties: {
eventType: { type: 'string' },
group_id: { type: 'string' },
user_id: { type: 'string' },
event_type: { type: 'number' },
user_id: { type: ['number', 'string'] },
},
required: ['eventType'],
required: ['event_type','user_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
@@ -19,23 +18,12 @@ export class SetInputStatus extends BaseAction<Payload, any> {
actionName = ActionName.SetInputStatus;
async _handle(payload: Payload) {
let peer: Peer;
if (payload.group_id) {
peer = {
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id,
};
} else if (payload.user_id) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id);
if (!uid) throw new Error('uid is empty');
peer = {
chatType: ChatType.KCHATTYPEC2C,
peerUid: uid,
};
} else {
throw new Error('请指定 group_id 或 user_id');
}
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, parseInt(payload.eventType));
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('uid is empty');
const peer = {
chatType: ChatType.KCHATTYPEC2C,
peerUid: uid,
};
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type);
}
}
+3 -4
View File
@@ -38,11 +38,10 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw `头像${payload.file}设置失败,api无返回`;
}
// log(`头像设置返回:${JSON.stringify(ret)}`)
// @ts-ignore
if (ret['result'] == 1004022) {
if (ret.result as number == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式`;
} else if (ret['result'] != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
} else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
}
} else {
fs.unlink(path, () => { });
+17 -7
View File
@@ -5,9 +5,11 @@ import { promises as fs } from 'fs';
import { decode } from 'silk-wasm';
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
interface Payload extends GetFilePayload {
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
}
const out_format = ['mp3' , 'amr' , 'wma' , 'm4a' , 'spx' , 'ogg' , 'wav' , 'flac'];
type Payload = {
out_format : string
} & GetFilePayload
export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord;
@@ -17,12 +19,19 @@ export default class GetRecord extends GetFileBase {
if (payload.out_format && typeof payload.out_format === 'string') {
const inputFile = res.file;
if (!inputFile) throw new Error('file not found');
if (!out_format.includes(payload.out_format)) {
throw new Error('转换失败 out_format 字段可能格式不正确');
}
const pcmFile = `${inputFile}.pcm`;
const outputFile = `${inputFile}.${payload.out_format}`;
try {
await fs.access(inputFile);
await this.decodeFile(inputFile, pcmFile);
await this.convertFile(pcmFile, outputFile, payload.out_format);
try {
await fs.access(outputFile);
} catch (error) {
await this.decodeFile(inputFile, pcmFile);
await this.convertFile(pcmFile, outputFile, payload.out_format);
}
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile;
res.url = outputFile;
@@ -48,7 +57,8 @@ export default class GetRecord extends GetFileBase {
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
return new Promise((resolve, reject) => {
const ffmpeg = spawn(FFMPEG_PATH, ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile]);
const params = format === 'amr' ? ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, '-ar', '8000', '-b:a', '12.2k', outputFile] : ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile];
const ffmpeg = spawn(FFMPEG_PATH, params);
ffmpeg.on('close', (code) => {
if (code === 0) {
@@ -63,4 +73,4 @@ export default class GetRecord extends GetFileBase {
});
});
}
}
}
@@ -17,9 +17,6 @@ export class GoCQHTTPGetGroupAtAllRemain extends BaseAction<Payload, any> {
async _handle(payload: Payload) {
const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString());
if (!ret.atInfo || ret.result !== 0) {
throw new Error('atInfo not found');
}
const data = {
can_at_all: ret.atInfo.canAtAll,
remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup,
@@ -25,6 +25,11 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
if (!uid) uid = extendData.detail.uid;
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
return {
...extendData.detail.simpleInfo.coreInfo,
...extendData.detail.commonExt ?? {},
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags ?? {},
...extendData.detail.simpleInfo.status ?? {},
user_id: parseInt(extendData.detail.uin) ?? 0,
uid: info.uid ?? uid,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
@@ -33,7 +38,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel),
sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown,
long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick,
reg_time: extendData.detail.commonExt.regTime ?? info.regTime,
reg_time: extendData.detail.commonExt?.regTime ?? info.regTime,
is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag,
is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag,
vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel,
@@ -31,15 +31,15 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path);
fs.unlink(path, () => { });
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
}
if (ret['result'] == 1004022) {
if (ret.result as number == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
} else if (ret['result'] != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
} else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
}
return ret;
} else {
+28
View File
@@ -0,0 +1,28 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
const SchemaData = {
type: 'object',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
actionName = ActionName.GetAiRecord;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody![0].index as NapProtoEncodeStructType<typeof IndexNode>);
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupShutList extends BaseAction<Payload, OB11Group> {
export class GetGroupShutList extends BaseAction<Payload, any> {
actionName = ActionName.GetGroupShutList;
payloadSchema = SchemaData;
@@ -0,0 +1,40 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {uri2local} from "@/common/file";
import {ChatType, Peer} from "@/core";
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
const SchemaData = {
type: 'object',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
message_id: string
}> {
actionName = ActionName.SendGroupAiRecord;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody![0].index as NapProtoEncodeStructType<typeof IndexNode>);
const { path, fileName, errMsg, success} = (await uri2local(this.core.NapCatTempPath, url));
if (!success) {
throw new Error(errMsg);
}
const peer = {chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString()} as Peer;
const element = await this.core.apis.FileApi.createValidSendPttElement(path);
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
return {message_id: sendRes.msgId};
}
}
+8
View File
@@ -98,6 +98,10 @@ import { GoCQHTTPCheckUrlSafely } from './go-cqhttp/GoCQHTTPCheckUrlSafely';
import { GoCQHTTPGetModelShow } from './go-cqhttp/GoCQHTTPGetModelShow';
import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow';
import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend';
import { GetMiniAppArk } from "@/onebot/action/extends/GetMiniAppArk";
import { GetAiRecord } from "@/onebot/action/group/GetAiRecord";
import { SendGroupAiRecord } from "@/onebot/action/group/SendGroupAiRecord";
import { GetAiCharacters } from "@/onebot/action/extends/GetAiCharacters";
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -210,6 +214,10 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
// new UploadForwardMsg(obContext, core),
new GetGroupShutList(obContext, core),
new GetGroupFileUrl(obContext, core),
new GetMiniAppArk(obContext, core),
new GetAiRecord(obContext, core),
new SendGroupAiRecord(obContext, core),
new GetAiCharacters(obContext, core),
];
const actionMap = new Map();
for (const action of actionHandlers) {
+4 -6
View File
@@ -11,8 +11,8 @@ import { decodeCQCode } from '@/onebot/cqcode';
import { MessageUnique } from '@/common/message-unique';
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
import BaseAction from '../BaseAction';
import { rawMsgWithSendMsg } from "@/core/packet/msg/converter";
import { PacketMsg } from "@/core/packet/msg/message";
import { rawMsgWithSendMsg } from "@/core/packet/message/converter";
import { PacketMsg } from "@/core/packet/message/message";
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { stringifyWithBigInt } from "@/common/helper";
@@ -236,12 +236,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: RawMessage | null,
res_id?: string
}> {
let returnMsg: RawMessage | undefined, res_id: string | undefined;
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
res_id = uploadReturnData?.res_id;
const res_id = uploadReturnData?.res_id;
const finallySendElements = uploadReturnData?.finallySendElements;
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
return { message: returnMsg ?? null, res_id };
}
@@ -276,7 +275,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
continue;
}
// @ts-ignore
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) {
nodeMsgIds.push(nodeMsg.message!.msgId);
+5 -1
View File
@@ -134,7 +134,11 @@ export enum ActionName {
GetGuildProfile = 'get_guild_service_profile',
GetGroupIgnoredNotifies = 'get_group_ignored_notifies',
SetGroupSign = "set_group_sign",
GetMiniAppArk = "get_mini_app_ark",
// UploadForwardMsg = "upload_forward_msg",
GetAiRecord = "get_ai_record",
GetAiCharacters = "get_ai_characters",
SendGroupAiRecord = "send_group_ai_record",
}
+34 -16
View File
@@ -24,7 +24,6 @@ import {
OB11MessageData,
OB11MessageDataType,
OB11MessageFileBase,
OB11MessageForward,
} from '@/onebot';
import { OB11Entities } from '@/onebot/entities';
import { EventType } from '@/onebot/event/OB11BaseEvent';
@@ -218,20 +217,34 @@ export class OneBotMsgApi {
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
return createReplyData(records.msgId);
}
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr])).msgList;
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// 我猜测可能是时间参数未对上 导致找不到引用消息 或者msgList 存在问题
this.core.context.logger.logError.bind(this.core.context.logger)(
'筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
element.replayMsgSeq,
',消息长度:',
replyMsgList.length
);
replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
}
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logWarn.bind(this.core.context.logger)(
'初次筛选消息失败,获取不到引用的消息 Seq:',
'筛选消息失败,将使用Fallback-2 Seq:',
element.replayMsgSeq,
',消息长度:',
replyMsgList.length
);
// 再次筛选
replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
}
// 丢弃该消息段
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logError.bind(this.core.context.logger)(
'最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
@@ -327,11 +340,11 @@ export class OneBotMsgApi {
},
multiForwardMsgElement: async (_, msg) => {
const message_data: OB11MessageForward = {
data: {} as any,
type: OB11MessageDataType.forward,
};
message_data.data.id = msg.msgId;
// const message_data: OB11MessageForward = {
// data: {} as any,
// type: OB11MessageDataType.forward,
// };
// message_data.data.id = msg.msgId;
const parentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType,
guildId: '',
@@ -743,9 +756,12 @@ export class OneBotMsgApi {
async (element) => {
for (const key in element) {
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
const parsedElement = await this.rawToOb11Converters[key]?.(
// eslint-disable-next-line
// @ts-ignore
const converters = this.rawToOb11Converters[key] as (
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
msg: RawMessage,
elementWrapper: MessageElement,
) => PromiseLike<OB11MessageData | null>;
const parsedElement = await converters?.(
element[key],
msg,
element,
@@ -794,9 +810,11 @@ export class OneBotMsgApi {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
}
const callResult = this.ob11ToRawConverters[sendMsg.type](
// eslint-disable-next-line
// @ts-ignore
const converter = this.ob11ToRawConverters[sendMsg.type] as (
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
context: MessageContext,
) => Promise<SendMessageElement | undefined>;
const callResult = converter(
sendMsg,
{ peer, deleteAfterSentFiles },
)?.catch(undefined);
+3 -3
View File
@@ -68,13 +68,13 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type;
for (const name in data.data) {
const value = (data.data as any)[name];
const value = (data.data as Record<string, unknown>)[name];
if (value === undefined) {
continue;
}
try {
const text = value.toString();
result += `,${name}=${CQCodeEscape(text)}`;
const text = value?.toString();
if (text) result += `,${name}=${CQCodeEscape(text)}`;
} catch (error) {
// If it can't be converted, skip this name-value pair
}
+11 -7
View File
@@ -311,15 +311,19 @@ export class NapCatOneBot11Adapter {
}
};
const msgIdSend = new LRUCache<string, boolean>(100);
const msgIdSend = new LRUCache<string, number>(100);
const recallMsgs = new LRUCache<string, boolean>(100);
msgListener.onAddSendMsg = async msg => {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
msgIdSend.put(msg.msgId, 0);
}
};
msgListener.onMsgInfoListUpdate = async msgList => {
this.emitRecallMsg(msgList, recallMsgs)
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && !msgIdSend.get(msg.msgId)) {
msgIdSend.put(msg.msgId, true);
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && msgIdSend.get(msg.msgId) == 0) {
msgIdSend.put(msg.msgId, 1);
// 完成后再post
this.apis.MsgApi.parseMessage(msg)
.then((ob11Msg) => {
@@ -341,7 +345,7 @@ export class NapCatOneBot11Adapter {
};
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
proxiedListenerOf(msgListener, this.context.logger),
);
}
@@ -370,7 +374,7 @@ export class NapCatOneBot11Adapter {
};
this.context.session.getBuddyService().addKernelBuddyListener(
proxiedListenerOf(buddyListener, this.context.logger) as any,
proxiedListenerOf(buddyListener, this.context.logger),
);
}
@@ -523,7 +527,7 @@ export class NapCatOneBot11Adapter {
);
}
private async emitMsg(message: RawMessage) {
private async emitMsg(message: RawMessage, parseEvent: boolean = true) {
const { debug, reportSelfMessage, messagePostFormat } = this.configLoader.configData;
this.context.logger.logDebug('收到新消息 RawMessage', message);
this.apis.MsgApi.parseMessage(message, messagePostFormat).then((ob11Msg) => {
+3 -3
View File
@@ -64,9 +64,9 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
});
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
// @ts-ignore
this.app.use((req, res) => this.handleRequest(req, res));
this.app.use(async (req, res, _) => {
await this.handleRequest(req, res);
});
this.server.listen(this.port, () => {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
});
+1 -1
View File
@@ -143,7 +143,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
private registerHeartBeat() {
this.heartbeatIntervalId = setInterval(() => {
this.wsClientsMutex.runExclusive(async () => {
this.wsClients.forEach((wsClient) => {
this.wsClientWithEvent.forEach((wsClient) => {
if (wsClient.readyState === WebSocket.OPEN) {
wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online, true)));
}
+1 -1
View File
@@ -165,7 +165,7 @@ export async function NCoreInitShell() {
logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args);
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
const isConnect = loginService.connect();
if (!isConnect) {
logger.logError.bind(logger)('核心登录服务连接失败!');
+1 -1
View File
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V3.2.12', 'napcat-update-button', 'secondary'),
SettingButton('V3.3.12', 'napcat-update-button', 'secondary'),
),
]),
SettingList([
+1 -1
View File
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V3.2.12", "napcat-update-button", "secondary")
SettingButton("V3.4.5", "napcat-update-button", "secondary")
)
]),
SettingList([