Merge remote-tracking branch 'origin/master'

This commit is contained in:
liujiahua123123 2019-09-10 19:52:26 +08:00
commit d05cecefc5
79 changed files with 1935 additions and 924 deletions

BIN
.README_images/68f8fec9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
.github/A}YWVE860U(%YQD$R1GB1[P.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
.github/J]CE)IK4BU08(EO~UVLJ{[F.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
.github/event hook.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -3,37 +3,48 @@
一个以<b>TIM QQ协议</b>驱动的JAVA(+Kotlin) QQ机器人服务端核心
我们坚持免费与开源
项目处于快速开发阶段, 现在已经可以接受和发送群聊/好友消息.
项目处于快速开发阶段
协议来自网络上开源项目
一切开发旨在学习, 请勿用于非法用途
<br>
### 抢先体验
现在你可以使用 Mirai 内置的一些测试qq号体验 Mirai, 但我们现在还不建议你使用自己的 qq 号登录
1. Clone
2. Import as Maven project
3. Run [MiraiMain](mirai-core/src/main/java/net/mamoe/mirai/MiraiMain.java#L7)
A JAVA(+Kotlin) powered open-source project under GPL license<br>
It use protocols from <i>TIM QQ</i>, that is, it won't be affected by the close of <i>Smart QQ</i><br>
The project is all for <b>learning proposes</b> and still in <b>developing stage</b><br>
#### 事件 Hook (Kotlin)
![event hook.png](.github/event%20hook.png)
![AYWVE86P](.github/A%7DYWVE860U%28%25YQD%24R1GB1%5BP.png)
#### 图片测试
**现在可以接受图片消息**(并解析为消息链):
![JsssF](.github/J%5DCE%29IK4BU08%28EO~UVLJ%7B%5BF.png)
![](.README_images/68f8fec9.png)
不过我们还正在努力做发送图片
### 代码结构
Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
与插件相关性强(或其他在二次开发中容易接触)的部分使用 Java 完成,
同时也会针对kotlin提供优化的方法调用. 例如对'+'操作符的重载: `String+BufferedImage+QQ.At+Face+URL+String+File` 将会被自动处理为String消息.
与插件相关性强(或其他在二次开发中容易接触)的部分尽量使用 Java 完成,
若使用 Kotlin, 我们会通过 Java interface 实现或 javadoc 帮助未接触过 Kotlin 的开发者.
即使你完全不了解 Kotlin, 你也可以正常开发.
### TODO
## TODO
- [x] 事件(Event)模块
- [ ] 插件(Plugin)模块
- [ ] 插件(Plugin)模块 **(Working on)**
- [x] Network - Touch
- [X] Network - Login
- [X] Network - Session
- [ ] Network - Verification Code **(Working on)**
- [X] Network - Verification Code
- [X] Network - Message Receiving
- [X] Network - Message Sending
- [ ] Network - Events **(Working on)**
- [ ] Robot - Friend/group list
- [ ] Robot - Actions(joining group, adding friend, etc.)
- [ ] Bot - Friend/group list
- [ ] Bot - Actions(joining group, adding friend, etc.)
- [ ] Message Section **(Working on)**
- [ ] Contact
- [ ] UI **(Working on)**
- [ ] UI
<br>
@ -42,18 +53,24 @@ Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
- Java 11 或更高
- Kotlin 1.3 或更高
### 插件开发
``` php
``` text
to be continued
...
```
<br>
A JAVA(+Kotlin) powered open-source project under GPL license<br>
It use protocols from <i>TIM QQ</i>, that is, it won't be affected by the close of <i>Smart QQ</i><br>
The project is all for <b>learning proposes</b> and still in <b>developing stage</b><br>
## Usage
### Requirements
- Java 11 or higher
- Kotlin 1.3 or higher
### Plugin Development
``` php
``` text
to be continued
...
```

View File

@ -1,72 +0,0 @@
g_count = 0
paccket sent: 02 37 13 08 25 31 01 76 E4 B8 DD 03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D 9F A7 43 90 2A 9D 29 B5 EA DB 50 7F D3 78 1C AE 31 7E 7E 4F A9 1B B5 C9 8D A8 4C 78 98 13 E1 45 FC 35 2E 22 3D E0 39 1A 3F C6 8B CA 06 A8 F3 B3 6F 95 D8 64 1A B0 E9 29 06 DB 5C F4 9B 32 47 5A B7 10 57 C5 2F C9 D9 7B 17 22 7F 09 A6 8C 30 04 24 0F 1D 61 A1 42 E2 7A AA 15 36 AC 67 9B 7A 4D 42 14 AD F5 2D D2 A3 CA 03
DataArrived >>
DataArrived >> flag = 08 25 31 01
DataArrived >> dispose_0825 >>
DataArrived >> dispose_0825 >> redirect
DataArrived >> dispose_0825 >> g_server = 125.39.132.167
DataArrived >> dispose_0825 >> g_count = 0
DataArrived >> dispose_0825 >> paccket sent: 02 37 13 08 25 31 02 76 E4 B8 DD 03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B 23 89 DB A3 07 80 49 63 01 76 69 F1 E1 11 32 06 E9 7F E4 6A 6B 98 07 75 EF 0E 1F 81 10 85 86 EB 96 8E 65 78 0F C3 BC F8 FF 51 3E 36 4F 48 3C 78 52 26 3F 4C 20 65 85 69 AC B8 36 B6 50 50 CC 01 4A 35 44 15 5C 80 B9 F7 A7 56 D4 B2 D4 A4 D9 09 56 29 93 39 0C C8 9C 0B F7 2D CE BE B0 D5 4C CE 48 B3 2D 18 28 A2 3C DD 26 C1 F1 6E A1 4B EC 8A 03
DataArrived >>
DataArrived >> flag = 08 25 31 02
DataArrived >> dispose_0825 >>
DataArrived >> dispose_0825 >> g_count = 0
DataArrived >> dispose_0825 >> 不需要redirect
DataArrived >> dispose_0825 >> m_loginTime = 5D 59 7D A6
DataArrived >> dispose_0825 >> m_loginIP = B7 5F F8 D4
DataArrived >> dispose_0825 >> m_0825token = 16 5A 4A C4 FE D1 F8 A3 CB B7 37 DD A5 AE 5C F7 04 74 36 91 4E CD 4A E6 EF 43 31 A7 D1 97 CC 6B 93 C7 9B 15 62 FD 11 3E 19 E1 69 62 B3 BC F4 9A E1 17 19 47 CC A3 1E AC
DataArrived >> dispose_0825 >> m_tgtgtKey = DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
DataArrived >> dispose_0825 >> g_count = 0
DataArrived >> dispose_0825 >> Construct_0836_622 >>
DataArrived >> dispose_0825 >> Construct_0836_622 >> PCName = DESKTOP-M17JREU
DataArrived >> dispose_0825 >> Construct_0836_622 >> PCName = 44 45 53 4B 54 4F 50 2D 4D 31 37 4A 52 45 55
DataArrived >> dispose_0825 >> Construct_0836_622 >> g_pass = xiaoqqq
DataArrived >> dispose_0825 >> Construct_0836_622 >> g_QQ = 76 E4 B8 DD
DataArrived >> dispose_0825 >> Construct_0836_622 >> crc32_code(Random) = 0C 69 01 0E FF CE E7 78 BA CA C7 66 AF 7B 07 22
DataArrived >> dispose_0825 >> Construct_0836_622 >> crc32_data = 8B D9 F6 A1
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >>
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >> m_tgtgtKey = DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >> packet = A9 4E DF FB 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 59 7D A6 00 00 00 00 00 00 00 00 00 00 00 00 00 B7 5F F8 D4 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
DataArrived >> dispose_0825 >> Construct_0836_622 >>
DataArrived >> dispose_0825 >> Construct_0836_622 >> PCName = DESKTOP-M17JREU
DataArrived >> dispose_0825 >> Construct_0836_622 >> PCName = 44 45 53 4B 54 4F 50 2D 4D 31 37 4A 52 45 55
DataArrived >> dispose_0825 >> Construct_0836_622 >> g_pass = xiaoqqq
DataArrived >> dispose_0825 >> Construct_0836_622 >> g_QQ = 76 E4 B8 DD
DataArrived >> dispose_0825 >> Construct_0836_622 >> crc32_code(Random) = 1B A6 09 08 3C CB 94 A1 9D 76 2C A0 B7 AC 98 44
DataArrived >> dispose_0825 >> Construct_0836_622 >> crc32_data = 57 4F 04 4B
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >>
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >> m_tgtgtKey = DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
DataArrived >> dispose_0825 >> Construct_0836_622 >> getTLV0006 >> packet = 7F E3 7A 1F 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 59 7D A6 00 00 00 00 00 00 00 00 00 00 00 00 00 B7 5F F8 D4 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
DataArrived >> dispose_0825 >> paccket sent: 02 37 13 08 36 31 03 76 E4 B8 DD 03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 00 00 00 10 EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA 12 58 AF 79 C5 60 54 5F A9 30 38 87 E9 B0 68 FA D3 83 A7 6A EA B6 7F 54 10 78 F0 47 60 24 1B E2 91 2D FD 60 F4 C7 DE 3C 7C 56 83 BE B4 66 49 60 5F E0 D3 2B 18 BD 5D 64 28 D1 98 8F 83 84 98 03 97 DE 97 83 5A BD 0B AC 1B 63 7C A2 C8 13 C2 26 8A C1 AA 6D B8 5D 4A 91 E7 C8 7B AF 3C 89 76 DF EA F3 F3 53 AD 69 2F 4C 45 90 69 B7 69 3E 05 C9 DE 1B B1 C9 DE D3 F6 4B 70 3D 27 54 BC D6 2B AB 68 13 2D E7 E3 11 FF 98 3F 1E 51 BC D6 F5 AB 26 DA 53 82 7B 3C 23 99 D8 77 95 32 64 C9 11 C5 8D 40 EA F6 E7 84 C6 B0 94 EE 4A 7E 22 1E 30 34 59 AB D1 66 79 EA A5 D4 AD A2 7D 4D 47 B8 FC 86 BC DD 5D 27 15 94 E0 1B 68 00 DD 5E 5A 09 08 E0 F5 91 EF 98 95 CC 92 B9 A0 EB AC 62 B5 5D DD AA EC 4F 36 48 6E C9 7C 2D 1F 21 98 F5 27 28 E5 8E 4A 51 BC 9A 2A BE 50 31 21 EC DF C8 97 35 58 76 B3 CD F9 92 7A 86 0E C4 1D 90 62 86 99 20 92 6C 12 C9 E2 E9 7F 0B 6B AC 59 00 55 7E B6 45 B1 C4 01 37 A6 1D B3 6E 16 06 96 40 59 CD 59 5D 6F 96 E9 B4 97 0D 55 AE 3B BF FA 54 73 D3 06 B3 47 AA 7E A1 89 F5 04 79 62 7C 11 B4 1C 4D F7 24 92 71 42 17 DC 52 67 9C 66 97 5F 64 1D CD 35 68 7D D5 D7 51 9B BA 29 92 E7 8B 6F B4 74 9E 84 54 5F E8 0D 81 89 15 FB 30 A0 1B AD B2 A3 46 3F F1 A7 A7 A1 A2 A6 D1 7D B0 4E D4 E9 87 AA 20 ED 9A 04 22 5F 57 45 20 05 2B 48 CD 06 4B BC 6F F2 92 D5 09 07 DF 83 DA FC 9D 75 50 C3 75 98 56 8C B3 B0 02 80 FD ED 61 03 00 86 EA E1 03 D2 08 68 B4 1F B9 9C EB 7B 75 9C 2D 94 10 F1 C0 40 E8 D9 9A DB 4A 0F 42 90 78 F6 AB 5B 7D 5A 18 ED 3F 45 8E 1F 98 D0 97 79 51 1D 2D 64 23 8D 30 93 FF C1 B2 05 1D 22 0C E6 51 CD F3 D5 F6 D9 DB 31 EC B2 2F B1 D1 ED F3 54 5F B3 F9 B9 74 0B 10 21 4D 84 52 CD 61 A2 39 51 CD 38 AF 2B DD BD CC 70 76 31 76 51 49 B7 03
DataArrived >>
DataArrived >> flag = 08 36 31 03
DataArrived >> Dispose_0836 >>
DataArrived >> Dispose_0836 >> m_0828_rec_decr_key = 37 61 4D 6D 48 73 77 6D 38 70 4B 23 6D 5F 21 5F
DataArrived >> Dispose_0836 >> nick_length = 20
DataArrived >> Dispose_0836 >> m_nick = (?ω)
DataArrived >> Dispose_0836 >> m_age = 5
DataArrived >> Dispose_0836 >> m_gender = 02
DataArrived >> Dispose_0836 >> g_clientKey = 00015D597DA60068666D5741D077D580228800B480E93195D2165593A44A6D42D81D38AC1A1E914F89D0B9FC15DFADB2D7257DF2B15D90FB2F6B4B9CF0EE4C38C9556C8FFCA43E688FD0CEE570350500A626547C4A76CFAF7586AC1B2CE400A93F9384FF7037B11FD5602EFAE870CD0F
DataArrived >> Dispose_0836 >> token38 = BA 24 BF EA 76 94 2C 9B 91 A8 8F 0E 7C EC F5 41 77 3C B9 D4 95 50 F2 00 FD CB E3 48 36 FB 89 13 CE E4 EA 76 A2 2F 20 86 F6 0F E0 54 55 6E D4 0B 9B EA 07 6B E1 D4 87 56
DataArrived >> Dispose_0836 >> token88 = 00 04 5D 59 7D A6 B7 5F F8 D4 00 00 00 00 00 78 AA 32 D3 89 86 C9 B0 41 6F 37 4F 2C 51 BA EC 9A C7 38 05 91 5C D9 3E 13 FC 5F E7 77 D0 A1 E8 B3 40 E3 3E 4E 27 B8 C2 0E F9 62 67 FA 65 E1 C9 DB F3 0B A5 F0 4B 13 7A B6 EA 1D 3C AD 8C 34 D4 3B FD 75 0C FE F5 4B 28 33 76 57 AA 68 F9 94 E1 72 41 D1 9C E5 D4 7C C6 2C 25 C5 07 A5 42 95 51 2F E0 88 41 DE 3E 9D 4F 4D 70 32 5E 44 28 5C 88 DA A6 8F 13 2B 79 C8 93 1D
DataArrived >> Dispose_0836 >> encryptionKey = C9 E2 F2 CB 45 79 DE F7 6C 51 7C 9B 97 CC D0 47
DataArrived >> Dispose_0836 >> g_count = 0
DataArrived >> Dispose_0836 >> Construct_0828 >>
DataArrived >> Dispose_0836 >> paccket sent: 02 37 13 08 28 04 34 76 E4 B8 DD 02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A 00 38 BA 24 BF EA 76 94 2C 9B 91 A8 8F 0E 7C EC F5 41 77 3C B9 D4 95 50 F2 00 FD CB E3 48 36 FB 89 13 CE E4 EA 76 A2 2F 20 86 F6 0F E0 54 55 6E D4 0B 9B EA 07 6B E1 D4 87 56 F9 99 8F FA 12 8E 22 A6 5A 9D A6 DC C9 B6 5C 5A EC BE BF CC 38 BD E1 5A 23 21 CE 02 31 F1 E1 BD FB 8E 4D E9 59 E6 BB FB B2 36 0C 47 0A C0 F7 94 63 C3 2F AB 6E AE 00 01 1F 5F 60 8E B5 79 97 EB 10 59 A0 29 B3 3B 9C BA 5D 33 C4 2A 57 CA CF 94 7A 2D DD F7 B3 9C BC 65 5D D5 62 53 A8 1D D2 F1 5B DD D7 24 32 63 60 60 DD 33 1F 3A C0 71 38 86 BC 78 D3 7C 7A E1 97 71 AB B7 59 AD 27 32 D5 AF F3 DC 1B 7B 70 3B 08 C0 91 D8 BC F1 C4 DA E3 DA 86 A1 27 8A EE C3 5F D6 25 42 A0 CB 19 7F 08 80 F8 65 2C 27 31 B7 D4 85 C3 49 BD 99 48 FE A9 63 78 6D 18 C3 4E BB F7 8A C4 80 8C 8A 17 EB 47 AF 2A 12 73 71 08 A6 E7 C3 08 2B 9A 6F 8A C2 6C 3B 1A CF 05 D8 57 63 33 AC BD 45 98 C1 85 56 08 0F 9F 36 FD 60 69 BC D0 94 1A 11 4D C6 3E 78 1D F1 67 D2 1D C4 C8 17 2F DC F4 B6 4F 5F F5 EE 8B 73 68 AA 3B BA C6 94 C8 21 1E 95 6D C2 7A BE 8B 1D 92 21 8D 2C D8 B6 86 D1 30 BB 72 34 B9 A0 D2 2E 4C 98 3C 17 E2 B2 6A AD 75 E8 B0 DE F4 1A 6F 15 93 47 B3 4D DA 6F BE A3 47 D3 9B 58 2D 4B A3 76 0E 39 ED A5 C3 0A 34 BA 78 01 AE 20 A3 38 CE BA CD F6 D7 1B C9 E7 4C 83 6E 31 34 25 16 64 BA EE 4B 8D E7 0E 2F C8 08 72 50 AE 91 16 7F 68 14 60 7E D8 3F CC 26 2D F6 BC 65 72 C8 F4 EA 55 E6 1B E0 BF F4 9F 9C FD A9 93 B6 62 78 F0 A1 19 D2 87 6E B8 B7 E3 70 13 09 95 29 C9 05 EC 99 36 5C 96 47 C1 C4 06 5C 23 5C A3 AD B0 39 BC 70 75 3D AA E9 16 03 0E 62 1B D0 78 EA F2 5C FD 9C 04 D9 AB 75 00 F8 37 F1 A8 DD 7B 65 91 D3 58 DE C5 BA 9E AC 13 DE 35 BA 17 DC D1 AB A5 96 C4 99 81 8E 21 4B 2F C1 9B 4C E1 56 A7 5D AC 26 71 EC 49 F0 A6 B1 F5 43 EA AC BE E6 9F A0 C2 E1 68 35 97 7B 81 76 AF 9E BD BC A7 D8 9E FC C0 E8 21 B0 BA 20 6A D0 BD E7 00 59 06 61 A1 DF AA 9F BA F4 5D A6 7B 5B A1 D8 6B B5 E9 72 66 51 8A D3 CE 51 A9 08 C7 11 4B FB 29 2E 6C 48 5B 8A 50 C6 5D 3A C1 9E A1 51 B6 56 DD 6B F5 D2 FD AE AB A4 4A A8 1F 99 BA 7D 4F 62 D7 64 22 31 04 62 36 62 65 96 B3 5A 35 03
DataArrived >>
DataArrived >> flag = 08 28 04 34
DataArrived >> Dispose_0828 >>
DataArrived >> Dispose_0828 >> g_count = 0
DataArrived >> Dispose_0828 >> g_sessionKey = D1 ED 7E 0B 6B BC 6F F0 2C 7E 31 8F 58 49 6D 20
DataArrived >> Dispose_0828 >> g_tlv0105 = 01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00 C2 D9 3F A5 A0 1B 6C 03 A2 EF AB CB 42 92 44 8E 15 97 28 1F DE B6 E9 0A 5C 53 01 CE A2 CC 95 3F E0 CB 30 3F 5C 67 09 22 83 CC 8A 80 8F D6 26 F5 EF EC 24 15 95 8E CE 99 00 40 02 02 03 3C 01 03 00 00 A1 4D 57 52 9E 5B 1F BB 48 75 09 67 F8 C0 64 F6 9B 2A 44 61 78 29 C1 26 9C 3C 59 0E DF 9B D1 59 97 0B 0C 2B 09 27 C6 7C 20 63 11 02 E1 4E A4 DE E2 59 CF A7 A1 47 0A B6
DataArrived >> Dispose_0828 >> g_loginStatus = 0A
DataArrived >> Dispose_0828 >> paccket sent: 02 37 13 00 EC 6E 8E 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 C4 28 24 D6 67 13 CE 5F F7 F8 38 79 F4 56 1F CA 13 95 22 4D 7B 5D B6 59 03
DataArrived >>
DataArrived >> flag = 00 EC 6E 8E
DataArrived >> g_count = 0
DataArrived >> paccket sent: 02 37 13 00 1D C5 CB 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 F3 B2 B9 BF F9 C9 87 EB C2 33 FD BA 6B 16 44 E8 B2 C1 8C 7E 4F 97 01 13 88 D8 00 BF 5F 6C 38 22 E0 50 4F 9B 73 7F 5F 31 64 72 9A C1 11 79 F5 B9 33 C0 EC 81 5E F7 D5 A4 BF C6 29 9F 18 9E C0 99 CE B7 16 E5 E8 BF EE E7 5A C3 5C 28 68 3E 48 18 03
DataArrived >>
DataArrived >> flag = 00 1D C5 CB
DataArrived >> paccket sent: 02 37 13 00 5C 7B 2E 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 E7 E2 64 22 9C 2F 33 27 A3 8B 4D 9C DE C5 A8 0D 03
DataArrived >>
DataArrived >> flag = 00 5C 7B 2E

37
mirai-api/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
</parent>
<artifactId>mirai-api</artifactId>
<version>1.0</version>
<dependencies>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
</plugins>
</build>
</project>

37
mirai-console/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
</parent>
<artifactId>mirai-console</artifactId>
<version>1.0</version>
<dependencies>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
</plugins>
</build>
</project>

View File

@ -9,10 +9,6 @@
<packaging>jar</packaging>
<properties>
<kotlin.version>1.3.41</kotlin.version>
</properties>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
@ -21,7 +17,6 @@
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core -->
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
@ -31,42 +26,36 @@
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.3.41</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
@ -94,63 +83,16 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -3,47 +3,59 @@ package net.mamoe.mirai;
import lombok.Getter;
import net.mamoe.mirai.contact.Group;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.network.RobotNetworkHandler;
import net.mamoe.mirai.network.BotNetworkHandler;
import net.mamoe.mirai.utils.BotAccount;
import net.mamoe.mirai.utils.ContactList;
import net.mamoe.mirai.utils.RobotAccount;
import net.mamoe.mirai.utils.config.MiraiConfigSection;
import org.jetbrains.annotations.NotNull;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
* <br>
* {@link Robot} 2 个模块组成.
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Robot#contacts} 访问
* {@linkplain RobotNetworkHandler 网络处理器}: 可通过 {@link Robot#network} 访问
* {@link Bot} 2 个模块组成.
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
* <br>
* 另外地, 若你需要得到机器人的 QQ 账号, 请访问 {@link Robot#account}
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Robot#instances}
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Bot#instances}
*
* @author Him188moe
* @author NatrualHG
* @see net.mamoe.mirai.contact.Contact
*
* <p>
* Robot that is the base of the whole program.
* Bot that is the base of the whole program.
* It contains a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group}.
*/
public final class Robot implements Closeable {
public static final List<Robot> instances = Collections.synchronizedList(new LinkedList<>());
public final class Bot implements Closeable {
public static final List<Bot> instances = Collections.synchronizedList(new LinkedList<>());
public final RobotAccount account;
{
instances.add(this);
}
public final int id = _id.getAndAdd(1);
public final BotAccount account;
public final ContactSystem contacts = new ContactSystem();
public final RobotNetworkHandler network;
public final BotNetworkHandler network;
@Override
public String toString() {
return String.format("Bot{id=%d,qq=%d}", id, this.account.qqNumber);
}
/**
* Robot 联系人管理.
* Bot 联系人管理.
*
* @see Robot#contacts
* @see Bot#contacts
*/
public final class ContactSystem {
private final ContactList<Group> groups = new ContactList<>();
@ -55,14 +67,14 @@ public final class Robot implements Closeable {
public QQ getQQ(long qqNumber) {
if (!this.qqs.containsKey(qqNumber)) {
this.qqs.put(qqNumber, new QQ(Robot.this, qqNumber));
this.qqs.put(qqNumber, new QQ(Bot.this, qqNumber));
}
return this.qqs.get(qqNumber);
}
public Group getGroupByNumber(long groupNumber) {
if (!this.groups.containsKey(groupNumber)) {
this.groups.put(groupNumber, new Group(Robot.this, groupNumber));
this.groups.put(groupNumber, new Group(Bot.this, groupNumber));
}
return groups.get(groupNumber);
}
@ -83,9 +95,9 @@ public final class Robot implements Closeable {
return owners.contains(ownerName);
}
public Robot(MiraiConfigSection<Object> data) throws Throwable {
public Bot(MiraiConfigSection<Object> data) throws Throwable {
this(
new RobotAccount(
new BotAccount(
data.getLongOrThrow("account", () -> new IllegalArgumentException("account")),
data.getStringOrThrow("password", () -> new IllegalArgumentException("password"))
),
@ -93,12 +105,12 @@ public final class Robot implements Closeable {
);
}
public Robot(@NotNull RobotAccount account, @NotNull List<String> owners) {
public Bot(@NotNull BotAccount account, @NotNull List<String> owners) {
Objects.requireNonNull(account);
Objects.requireNonNull(owners);
this.account = account;
this.owners = Collections.unmodifiableList(owners);
this.network = new RobotNetworkHandler(this);
this.network = new BotNetworkHandler(this);
}
@ -109,5 +121,12 @@ public final class Robot implements Closeable {
this.contacts.qqs.clear();
}
public void addFriend(long qq) {
}
/* PRIVATE */
private static final AtomicInteger _id = new AtomicInteger(0);
}

View File

@ -6,8 +6,10 @@ import net.mamoe.mirai.event.events.server.ServerDisabledEvent;
import net.mamoe.mirai.event.events.server.ServerEnabledEvent;
import net.mamoe.mirai.network.packet.login.LoginState;
import net.mamoe.mirai.task.MiraiTaskManager;
import net.mamoe.mirai.utils.BotAccount;
import net.mamoe.mirai.utils.LoggerTextFormat;
import net.mamoe.mirai.utils.MiraiLogger;
import net.mamoe.mirai.utils.MiraiLoggerKt;
import net.mamoe.mirai.utils.config.MiraiConfig;
import net.mamoe.mirai.utils.config.MiraiConfigSection;
import net.mamoe.mirai.utils.setting.MiraiSettingListSection;
@ -16,7 +18,9 @@ import net.mamoe.mirai.utils.setting.MiraiSettings;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
/**
* @author NaturalHG
@ -150,9 +154,8 @@ public class MiraiServer {
MiraiConfigSection<Object> section = new MiraiConfigSection<>();
System.out.println("/");
Scanner scanner = new Scanner(System.in);
getLogger().info("Input a " + LoggerTextFormat.RED + " QQ number " + LoggerTextFormat.GREEN + "for default robotNetworkHandler");
getLogger().info("Input a " + LoggerTextFormat.RED + " QQ number " + LoggerTextFormat.GREEN + "for default botNetworkHandler");
getLogger().info("输入用于默认机器人的QQ号");
long qqNumber = scanner.nextLong();
getLogger().info("Input the password for that QQ account");
@ -173,30 +176,60 @@ public class MiraiServer {
getLogger().info(LoggerTextFormat.GREEN + "Server enabled; Welcome to Mirai");
getLogger().info("Mirai Version=" + MiraiServer.MIRAI_VERSION + " QQ Version=" + MiraiServer.QQ_VERSION);
getLogger().info("Initializing [Robot]s");
getLogger().info("Initializing [Bot]s");
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
getLogger().info("Initializing [Robot] " + section.getString("account"));
try {
Robot robot = new Robot(section);
var state = robot.network.tryLogin$mirai_core().get();
//robot.network.tryLogin$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCEED) {
Robot.instances.add(robot);
getAvailableBot();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
/*
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
getLogger().info("Initializing [Bot] " + section.getString("account"));
try {
Bot bot = new Bot(section);
var state = bot.network.tryLogin$mirai_core().get();
//bot.network.tryLogin$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) {
Bot.instances.add(bot);
getLogger().success(" Login Succeed");
} else {
getLogger().error(" Login Failed with error " + state);
robot.close();
bot.close();
}
// }).get();
} catch (Throwable e) {
e.printStackTrace();
getLogger().error("Could not load QQ robots config!");
getLogger().error("Could not load QQ bots config!");
System.exit(1);
}
});
});*/
}
String qqList = "3150499752----1234567890\n" +
"3119292829----987654321\n" +
"2399148773----12345678910\n" +
"3145561616----987654321\n" +
"2374150554----12345678910\n" +
"2375307394----12345678910\n" +
"2401645747----12345678910\n" +
"1515419818----1234567890\n" +
"3107367848----987654321\n";
private Bot getAvailableBot() throws ExecutionException, InterruptedException {
for (String it : qqList.split("\n")) {
var strings = it.split("----");
var bot = new Bot(new BotAccount(Long.parseLong(strings[0]), strings[1]), List.of());
if (bot.network.tryLogin$mirai_core().get() == LoginState.SUCCESS) {
MiraiLoggerKt.success(bot, "Login succeed");
return bot;
}
}
throw new RuntimeException();
}
}

View File

@ -1,21 +1,28 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
/**
* A contact is a [QQ] or a [Group] for one particular [Robot] instance only.
* A contact is a [QQ] or a [Group] for one particular [Bot] instance only.
*
* @param robot Owner [Robot]
* @param bot Owner [Bot]
* @author Him188moe
*/
abstract class Contact(val robot: Robot, val number: Long) {
abstract class Contact internal constructor(val bot: Bot, val number: Long) {
/**
* Async
*/
abstract fun sendMessage(message: Message)
abstract fun sendMessage(message: MessageChain)
fun sendMessage(message: Message) {
if (message is MessageChain) {
return sendMessage(message)
}
return sendMessage(message.toChain())
}
fun sendMessage(message: String) {
this.sendMessage(PlainText(message))

View File

@ -1,16 +1,27 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.utils.ContactList
import java.io.Closeable
class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
/**
*
*
* Java 获取 groupNumber: `group.getNumber()`
* Java 获取所属 bot: `group.getBot()`
* Java 获取群成员列表: `group.getMembers()`
* Java 获取 groupId: `group.getGroupId()`
*
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
*/
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
val groupId = groupNumberToId(number)
val members = ContactList<QQ>()
override fun sendMessage(message: Message) {
robot.network.messageHandler.sendGroupMessage(this, message)
override fun sendMessage(message: MessageChain) {
bot.network.messageHandler.sendGroupMessage(this, message)
}
override fun sendXMLMessage(message: String) {
@ -23,7 +34,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
companion object {
@JvmStatic
fun groupNumberToId(number: Long): Long {
fun groupNumberToId(number: Long): Long {//求你别出错
val left: Long = number.toString().let {
if (it.length < 6) {
return@groupNumberToId number
@ -61,7 +72,7 @@ class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
}
@JvmStatic
fun groupIdToNumber(id: Long): Long {
fun groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let {
if (it.length < 6) {
return@groupIdToNumber id

View File

@ -1,21 +1,25 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Robot
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.MessageChain
/**
* QQ 账号.
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Robot].
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
*
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive message from or send message to.
* Notice that, one QQ instance belong to one [Robot], that is, QQ instances from different [Robot] are NOT the same.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
class QQ(robot: Robot, number: Long) : Contact(robot, number) {
override fun sendMessage(message: Message) {
robot.network.messageHandler.sendFriendMessage(this, message)
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override fun sendMessage(message: MessageChain) {
bot.network.messageHandler.sendFriendMessage(this, message)
}
override fun sendXMLMessage(message: String) {

View File

@ -62,6 +62,6 @@ fun <C : KClass<E>, E : MiraiEvent> C.hookWhile(hook: (E) -> Boolean) {
private class MiraiEventHookSimple<E : MiraiEvent>(clazz: Class<E>, val hook: (E) -> Boolean) : MiraiEventHook<E>(clazz) {
override fun accept(event: MiraiEvent?): Boolean {
@Suppress("UNCHECKED_CAST")
return hook.invoke(event as E)
return !hook.invoke(event as E)
}
}

View File

@ -0,0 +1,21 @@
package net.mamoe.mirai.event.events.bot;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.event.MiraiEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public abstract class BotEvent extends MiraiEvent {
private final Bot bot;
public BotEvent(@NotNull Bot bot) {
this.bot = Objects.requireNonNull(bot);
}
@NotNull
public Bot getBot() {
return bot;
}
}

View File

@ -0,0 +1,18 @@
package net.mamoe.mirai.event.events.bot
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.MiraiEvent
/**
* @author Him188moe
*/
class BotLoginEvent(val bot: Bot) : MiraiEvent()
class BotLogoutEvent(val bot: Bot) : MiraiEvent()
class BotMessageReceivedEvent(val bot: Bot, val type: Type, val message: String) : MiraiEvent() {
enum class Type {
FRIEND,
GROUP
}
}

View File

@ -0,0 +1,10 @@
package net.mamoe.mirai.event.events.bot;
import net.mamoe.mirai.Bot;
public final class BotLoginSucceedEvent extends BotEvent {
public BotLoginSucceedEvent(Bot bot) {
super(bot);
}
}

View File

@ -1,18 +1,18 @@
package net.mamoe.mirai.event.events.group;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.contact.Group;
import net.mamoe.mirai.event.events.robot.RobotEvent;
import net.mamoe.mirai.event.events.bot.BotEvent;
import org.jetbrains.annotations.NotNull;
/**
* @author Him188moe
*/
public abstract class GroupEvent extends RobotEvent {
public abstract class GroupEvent extends BotEvent {
private final Group group;
public GroupEvent(Robot robot, Group group) {
super(robot);
public GroupEvent(Bot bot, Group group) {
super(bot);
this.group = group;
}

View File

@ -1,6 +1,6 @@
package net.mamoe.mirai.event.events.group;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.contact.Group;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.defaults.MessageChain;
@ -14,8 +14,8 @@ public final class GroupMessageEvent extends GroupEvent {
private final MessageChain messageChain;
private final String messageString;
public GroupMessageEvent(@NotNull Robot robot, @NotNull Group group, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(robot, group);
public GroupMessageEvent(@NotNull Bot bot, @NotNull Group group, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(bot, group);
this.sender = sender;
this.messageChain = messageChain;
this.messageString = messageChain.toString();

View File

@ -1,16 +1,17 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.event.Cancellable;
import net.mamoe.mirai.network.packet.ClientPacket;
import org.jetbrains.annotations.NotNull;
/**
* Packet 已经 {@link ClientPacket#encode()}, 即将被发送
* Packet 已经 encoded, 即将被发送
*
* @author Him188moe
*/
public final class BeforePacketSendEvent extends ClientPacketEvent implements Cancellable {
public BeforePacketSendEvent(@NotNull ClientPacket packet) {
super(packet);
public BeforePacketSendEvent(@NotNull Bot bot, @NotNull ClientPacket packet) {
super(bot, packet);
}
}

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.network.packet.ClientPacket;
import org.jetbrains.annotations.NotNull;
@ -7,8 +8,8 @@ import org.jetbrains.annotations.NotNull;
* @author Him188moe
*/
public abstract class ClientPacketEvent extends PacketEvent {
public ClientPacketEvent(@NotNull ClientPacket packet) {
super(packet);
public ClientPacketEvent(@NotNull Bot bot, @NotNull ClientPacket packet) {
super(bot, packet);
}
@Override

View File

@ -1,6 +1,7 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.event.MiraiEvent;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.event.events.bot.BotEvent;
import net.mamoe.mirai.network.packet.Packet;
import org.jetbrains.annotations.NotNull;
@ -9,10 +10,11 @@ import java.util.Objects;
/**
* @author Him188moe
*/
public abstract class PacketEvent extends MiraiEvent {
public abstract class PacketEvent extends BotEvent {
private final Packet packet;
public PacketEvent(@NotNull Packet packet) {
public PacketEvent(@NotNull Bot bot, @NotNull Packet packet) {
super(bot);
this.packet = Objects.requireNonNull(packet);
}

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.network.packet.ClientPacket;
import org.jetbrains.annotations.NotNull;
@ -9,7 +10,7 @@ import org.jetbrains.annotations.NotNull;
* @author Him188moe
*/
public final class PacketSentEvent extends ClientPacketEvent {
public PacketSentEvent(@NotNull ClientPacket packet) {
super(packet);
public PacketSentEvent(@NotNull Bot bot, @NotNull ClientPacket packet) {
super(bot, packet);
}
}

View File

@ -1,13 +1,14 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.network.packet.ServerPacket;
/**
* @author Him188moe
*/
public abstract class ServerPacketEvent extends PacketEvent {
public ServerPacketEvent(ServerPacket packet) {
super(packet);
public ServerPacketEvent(Bot bot, ServerPacket packet) {
super(bot, packet);
}
@Override

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.event.events.network;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.event.Cancellable;
import net.mamoe.mirai.network.packet.ServerPacket;
import net.mamoe.mirai.network.packet.ServerVerificationCodePacket;
@ -11,7 +12,7 @@ import net.mamoe.mirai.network.packet.ServerVerificationCodePacket;
* @author Him188moe
*/
public final class ServerPacketReceivedEvent extends ServerPacketEvent implements Cancellable {
public ServerPacketReceivedEvent(ServerPacket packet) {
super(packet);
public ServerPacketReceivedEvent(Bot bot, ServerPacket packet) {
super(bot, packet);
}
}

View File

@ -1,8 +1,8 @@
package net.mamoe.mirai.event.events.qq;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.event.events.robot.RobotEvent;
import net.mamoe.mirai.event.events.bot.BotEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@ -10,11 +10,11 @@ import java.util.Objects;
/**
* @author Him188moe
*/
public abstract class FriendEvent extends RobotEvent {
public abstract class FriendEvent extends BotEvent {
private final QQ qq;
public FriendEvent(@NotNull Robot robot, @NotNull QQ qq) {
super(robot);
public FriendEvent(@NotNull Bot bot, @NotNull QQ qq) {
super(bot);
this.qq = Objects.requireNonNull(qq);
}

View File

@ -1,6 +1,6 @@
package net.mamoe.mirai.event.events.qq;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.defaults.MessageChain;
import org.jetbrains.annotations.NotNull;
@ -12,19 +12,14 @@ import java.util.Objects;
*/
public final class FriendMessageEvent extends FriendEvent {
private final MessageChain messageChain;
private final String messageString;
public FriendMessageEvent(@NotNull Robot robot, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(robot, sender);
public FriendMessageEvent(@NotNull Bot bot, @NotNull QQ sender, @NotNull MessageChain messageChain) {
super(bot, sender);
this.messageChain = Objects.requireNonNull(messageChain);
this.messageString = messageChain.toString();
}
public String getMessageString() {
return messageString;
}
public MessageChain getMessageChain() {
@NotNull
public MessageChain message() {
return messageChain;
}
}

View File

@ -1,21 +0,0 @@
package net.mamoe.mirai.event.events.robot;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.event.MiraiEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public abstract class RobotEvent extends MiraiEvent {
private final Robot robot;
public RobotEvent(@NotNull Robot robot) {
this.robot = Objects.requireNonNull(robot);
}
@NotNull
public Robot getRobot() {
return robot;
}
}

View File

@ -1,18 +0,0 @@
package net.mamoe.mirai.event.events.robot
import net.mamoe.mirai.Robot
import net.mamoe.mirai.event.MiraiEvent
/**
* @author Him188moe
*/
class RobotLoginEvent(val robot: Robot) : MiraiEvent()
class RobotLogoutEvent(val robot: Robot) : MiraiEvent()
class RobotMessageReceivedEvent(val robot: Robot, val type: Type, val message: String) : MiraiEvent() {
enum class Type {
FRIEND,
GROUP
}
}

View File

@ -1,10 +0,0 @@
package net.mamoe.mirai.event.events.robot;
import net.mamoe.mirai.Robot;
public final class RobotLoginSucceedEvent extends RobotEvent {
public RobotLoginSucceedEvent(Robot robot) {
super(robot);
}
}

View File

@ -4,8 +4,147 @@ package net.mamoe.mirai.message;
* @author Him188moe
*/
public enum FaceID {
// TODO: 2019/9/1
Face_jingya(0),
Face_piezui(1),
Face_se(2),
Face_fadai(3),
Face_deyi(4),
Face_liulei(5),
Face_haixiu(6),
Face_bizui(7),
Face_shui(8),
Face_daku(9),
Face_ganga(10),
Face_fanu(11),
Face_tiaopi(12),
Face_ciya(13),
Face_weixiao(14),
Face_nanguo(15),
Face_ku(16),
Face_zhuakuang(18),
Face_tu(19),
Face_touxiao(20),
Face_keai(21),
Face_baiyan(22),
Face_aoman(23),
Face_ji_e(24),
Face_kun(25),
Face_jingkong(26),
Face_liuhan(27),
Face_hanxiao(28),
Face_dabing(29),
Face_fendou(30),
Face_zhouma(31),
Face_yiwen(32),
Face_yun(34),
Face_zhemo(35),
Face_shuai(36),
Face_kulou(37),
Face_qiaoda(38),
Face_zaijian(39),
Face_fadou(41),
Face_aiqing(42),
Face_tiaotiao(43),
Face_zhutou(46),
Face_yongbao(49),
Face_dan_gao(53),
Face_shandian(54),
Face_zhadan(55),
Face_dao(56),
Face_zuqiu(57),
Face_bianbian(59),
Face_kafei(60),
Face_fan(61),
Face_meigui(63),
Face_diaoxie(64),
Face_aixin(66),
Face_xinsui(67),
Face_liwu(69),
Face_taiyang(74),
Face_yueliang(75),
Face_qiang(76),
Face_ruo(77),
Face_woshou(78),
Face_shengli(79),
Face_feiwen(85),
Face_naohuo(86),
Face_xigua(89),
Face_lenghan(96),
Face_cahan(97),
Face_koubi(98),
Face_guzhang(99),
Face_qiudale(100),
Face_huaixiao(101),
Face_zuohengheng(102),
Face_youhengheng(103),
Face_haqian(104),
Face_bishi(105),
Face_weiqu(106),
Face_kuaikule(107),
Face_yinxian(108),
Face_qinqin(109),
Face_xia(110),
Face_kelian(111),
Face_caidao(112),
Face_pijiu(113),
Face_lanqiu(114),
Face_pingpang(115),
Face_shiai(116),
Face_piaochong(117),
Face_baoquan(118),
Face_gouyin(119),
Face_quantou(120),
Face_chajin(121),
Face_aini(122),
Face_bu(123),
Face_hao(124),
Face_zhuanquan(125),
Face_ketou(126),
Face_huitou(127),
Face_tiaosheng(128),
Face_huishou(129),
Face_jidong(130),
Face_jiewu(131),
Face_xianwen(132),
Face_zuotaiji(133),
Face_youtaiji(134),
Face_shuangxi(136),
Face_bianpao(137),
Face_denglong(138),
Face_facai(139),
Face_K_ge(140),
Face_gouwu(141),
Face_youjian(142),
Face_shuai_qi(143),
Face_hecai(144),
Face_qidao(145),
Face_baojin(146),
Face_bangbangtang(147),
Face_he_nai(148),
Face_xiamian(149),
Face_xiangjiao(150),
Face_feiji(151),
Face_kaiche(152),
Face_gaotiezuochetou(153),
Face_chexiang(154),
Face_gaotieyouchetou(155),
Face_duoyun(156),
Face_xiayu(157),
Face_chaopiao(158),
Face_xiongmao(159),
Face_dengpao(160),
Face_fengche(161),
Face_naozhong(162),
Face_dasan(163),
Face_caiqiu(164),
Face_zuanjie(165),
Face_shafa(166),
Face_zhijin(167),
Face_yao(168),
Face_shouqiang(169),
Face_qingwa(170),
// TODO: 2019/9/1 添加更多表情
;
@ -18,4 +157,15 @@ public enum FaceID {
public int getId() {
return id;
}
public static FaceID ofId(int id) {
for (FaceID value : FaceID.values()) {
if (value.id == id) {
return value;
}
}
return null;
}
}

View File

@ -1,79 +0,0 @@
package net.mamoe.mirai.message;
import net.mamoe.mirai.contact.Contact;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.defaults.At;
import net.mamoe.mirai.message.defaults.Image;
import net.mamoe.mirai.message.defaults.MessageChain;
import net.mamoe.mirai.message.defaults.PlainText;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Objects;
/**
* 可发送的或从服务器接收的消息.
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 {@linkplain PlainText 纯文本}, {@linkplain Image 图片} .
*
* @author Him188moe
* @see Contact#sendMessage(Message) 发送这个消息
* @see MessageKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
*/
public abstract class Message {
@Override
public abstract String toString();
/**
* 把这个消息连接到另一个消息的头部. 相当于字符串相加
* <p>
* Connects this Message to the head of another Message.
* That is, another message becomes the tail of this message.
* This method does similar to {@link String#concat(String)}
* <p>
* E.g.:
* PlainText a = new PlainText("Hello ");
* PlainText b = new PlainText("world");
* PlainText c = a.concat(b);
* <p>
* the text of c is "Hello world"
*
* @param tail tail
* @return message connected
*/
public Message concat(@NotNull Message tail) {
return new MessageChain(this, Objects.requireNonNull(tail));
}
public final Message concat(String tail) {
return concat(new PlainText(tail));
}
public Message withImage(String imageId) {
// TODO: 2019/9/1
return this;
}
public Message withImage(BufferedImage image) {
// TODO: 2019/9/1
return this;
}
public Message withImage(File image) {
// TODO: 2019/9/1
return this;
}
public Message withAt(@NotNull QQ target) {
this.concat(target.at());
return this;
}
public Message withAt(int target) {
this.concat(new At(target));
return this;
}
}

View File

@ -0,0 +1,184 @@
package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.Image
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
/**
* 可发送的或从服务器接收的消息.
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] .
*
* #### Kotlin 使用 [Message]
* 这与使用 [String] 的使用非常类似.
*
* 比较 [Message] [String] (使用 infix [Message.valueEquals]):
* `if(message valueEquals "你好") qq.sendMessage(message)`
*
* 连接 [Message] [Message], [String], [BufferedImage] (使用 operator [Message.plus]):
* ```
* message = PlainText("Hello ")
* qq.sendMessage(message + "world")
* ```
*
* @author Him188moe
* @see Contact.sendMessage
*/
abstract class Message {
internal abstract val type: Int
private var toStringCache: String? = null
private val cacheLock = object : Any() {}
internal abstract fun toStringImpl(): String
/**
* 得到用户层的文本消息. :
* - [PlainText] 得到 消息内容
* - [Image] 得到 "{ID}.png"
* - [At] 得到 "[@qq]"
*/
final override fun toString(): String {
synchronized(cacheLock) {
if (toStringCache != null) {
return toStringCache!!
}
this.toStringCache = toStringImpl()
return toStringCache!!
}
}
internal fun clearToStringCache() {
synchronized(cacheLock) {
toStringCache = null
}
}
/**
* 得到类似 "PlainText(内容)", "Image(ID)"
*/
open fun toObjectString(): String {
return this.javaClass.simpleName + String.format("(%s)", this.toString())
}
/**
* 转换为数据包使用的 byte array
*/
abstract fun toByteArray(): ByteArray
/**
* 比较两个 Message 的内容是否相等. :
* - [PlainText] 比较 [PlainText.text]
* - [Image] 比较 [Image.imageID]
*/
abstract infix fun valueEquals(another: Message): Boolean
/**
* 将这个消息的 [toString] [another] 比较
*/
infix fun valueEquals(another: String): Boolean = this.toString() == another
/**
* 把这个消息连接到另一个消息的头部. 相当于字符串相加
*
*
* Connects this Message to the head of another Message.
* That is, another message becomes the tail of this message.
* This method does similar to [String.concat]
*
*
* E.g.:
* PlainText a = new PlainText("Hello ");
* PlainText b = new PlainText("world");
* PlainText c = a.concat(b);
*
*
* the text of c is "Hello world"
*
* @param tail tail
* @return message connected
*/
open fun concat(tail: Message): Message {
return MessageChain(this, Objects.requireNonNull(tail))
}
fun concat(tail: String): Message {
return concat(PlainText(tail))
}
fun withImage(imageId: String): Message {
// TODO: 2019/9/1
return this
}
fun withImage(image: BufferedImage): Message {
// TODO: 2019/9/1
return this
}
fun withImage(image: File): Message {
// TODO: 2019/9/1
return this
}
fun withAt(target: QQ): Message {
this.concat(target.at())
return this
}
fun withAt(target: Int): Message {
this.concat(At(target.toLong()))
return this
}
open fun toChain(): MessageChain {
return MessageChain(this)
}
/* For Kotlin */
/**
* 实现使用 '+' 操作符连接 [Message] [Message]
*/
infix operator fun plus(another: Message): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [String]
*/
infix operator fun plus(another: String): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [Number]
*/
infix operator fun plus(another: Number): Message = this.concat(another.toString())
/**
* 连接 [String] [Message]
*/
fun String.concat(another: Message): Message = PlainText(this).concat(another)
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Message) return false
if (type != other.type) return false
return this.toString() == other.toString()
}
}

View File

@ -0,0 +1,22 @@
package net.mamoe.mirai.message
/**
* [Message] 在数据包中的 id([UByte])
*
* Java 调用方式:
* MessageId.at
*
* @author Him188moe
*/
object MessageId {
const val AT: Int = 0x00//todo 不知道是多少
const val FACE: Int = 0x00//todo 不知道是多少
const val TEXT: Int = 0x01
const val IMAGE: Int = 0x06
const val CHAIN: Int = 0xff//仅用于 equals. Packet 中不存在 Chain 概念
}

View File

@ -1,20 +0,0 @@
@file:JvmName("MessageKt")
package net.mamoe.mirai.message
import net.mamoe.mirai.message.defaults.PlainText
/**
* 实现使用 '+' 操作符连接 [Message] [Message]
*/
infix operator fun Message.plus(another: Message): Message = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [String]
*/
infix operator fun Message.plus(another: String): Message = this.concat(another)
/**
* 连接 [String] [Message]
*/
infix fun String.concat(another: Message): Message = PlainText(this).concat(another)

View File

@ -1,34 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.message.Message;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* At 一个人的消息.
*
* @author Him188moe
*/
public final class At extends Message {
private final long target;
public At(@NotNull QQ target) {
this(Objects.requireNonNull(target).getNumber());
}
public At(long target) {
this.target = target;
}
public long getTarget() {
return target;
}
@Override
public String toString() {
// TODO: 2019/9/4 At.toString
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,30 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
/**
* At 一个人
*
* @author Him188moe
*/
class At(val target: Long) : Message() {
override val type: Int = MessageId.AT
constructor(target: QQ) : this(target.number)
override fun toStringImpl(): String = "[@$target]"
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is At) {
return false
}
return another.target == this.target
}
}

View File

@ -1,27 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.FaceID;
import net.mamoe.mirai.message.Message;
/**
* QQ 自带表情
*
* @author Him188moe
*/
public final class Face extends Message {
private final FaceID id;
public Face(FaceID id) {
this.id = id;
}
public FaceID getId() {
return id;
}
@Override
public String toString() {
// TODO: 2019/9/1
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,33 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
/**
* QQ 自带表情
*
* @author Him188moe
*/
class Face(val id: FaceID?) : Message() {
override val type: Int = MessageId.FACE
override fun toStringImpl(): String {
return if (id == null) {
"[face?]"
} else String.format("[face%d]", id.id)
}
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is Face) {
return false
}
return this.id == another.id
}
}

View File

@ -1,43 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
/**
* @author Him188moe
*/
public final class Image extends Message {
public Image(InputStream inputStream) {
}
public Image(BufferedImage image) {
}
public Image(File imageFile) throws FileNotFoundException {
this(new FileInputStream(imageFile));
}
public Image(URL url) throws IOException {
this(ImageIO.read(url));
}
/**
* {xxxxx}.jpg
*
* @param imageID
*/
public Image(String imageID) {
}
@Override
public String toString() {
return null;
}
}

View File

@ -0,0 +1,57 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import java.awt.image.BufferedImage
import java.io.*
import java.net.URL
import javax.imageio.ImageIO
/**
* @author Him188moe
*/
class Image : Message {
override val type: Int = MessageId.IMAGE
private var imageID: String? = null
constructor(inputStream: InputStream) {
}
constructor(image: BufferedImage) {
}
@Throws(FileNotFoundException::class)
constructor(imageFile: File) : this(FileInputStream(imageFile)) {
}
@Throws(IOException::class)
constructor(url: URL) : this(ImageIO.read(url)) {
}
/**
* {xxxxx}.jpg
*
* @param imageID
*/
constructor(imageID: String) {
this.imageID = imageID
}
override fun toStringImpl(): String {
return imageID!!
}
override fun toByteArray(): ByteArray {
TODO()
}
override fun valueEquals(another: Message): Boolean {
if (another !is Image) {
return false
}
return this.imageID == another.imageID
}
}

View File

@ -1,53 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Him188moe
*/
public final class MessageChain extends Message {
private LinkedList<Message> list = new LinkedList<>();
public MessageChain(@NotNull Message head, @NotNull Message tail) {
Objects.requireNonNull(head);
Objects.requireNonNull(tail);
list.add(head);
list.add(tail);
}
public MessageChain(@NotNull Message message) {
Objects.requireNonNull(message);
list.add(message);
}
/**
* @return An unmodifiable list
*/
public List<Message> toList() {
return List.copyOf(list);
}
public Stream<Message> stream() {
return new ArrayList<>(list).stream();
}
@Override
public synchronized String toString() {
return this.list.stream().map(Message::toString).collect(Collectors.joining(""));
}
@Override
public synchronized Message concat(@NotNull Message tail) {
this.list.add(tail);
return this;
}
}

View File

@ -0,0 +1,86 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.utils.lazyEncode
import org.intellij.lang.annotations.MagicConstant
import java.util.*
import java.util.stream.Collectors
import java.util.stream.Stream
class MessageChain : Message {
override val type: Int = MessageId.CHAIN
internal val list = LinkedList<Message>()
constructor(head: Message, tail: Message) {
Objects.requireNonNull(head)
Objects.requireNonNull(tail)
list.add(head)
list.add(tail)
}
constructor(message: Message) {
Objects.requireNonNull(message)
list.add(message)
}
constructor()
fun toList(): List<Message> {
return list.toList()
}
fun size(): Int {
return list.size
}
@Synchronized
fun containsType(@MagicConstant(valuesFromClass = MessageId::class) type: Int): Boolean {
for (message in list) {
if (message.type == type) {
return true
}
}
return false
}
fun stream(): Stream<Message> {
return ArrayList(list).stream()
}
@Synchronized
override fun toStringImpl(): String {
return this.list.stream().map { it.toString() }.collect(Collectors.joining(""))
}
@Synchronized
override fun toObjectString(): String {
return String.format("MessageChain(%s)", this.list.stream().map { it.toObjectString() }.collect(Collectors.joining(", ")))
}
@Synchronized
override fun concat(tail: Message): Message {
this.list.add(tail)
clearToStringCache()
return this
}
override fun toChain(): MessageChain {
return this
}
override fun toByteArray(): ByteArray = lazyEncode {
stream().forEach { message ->
it.write(message.toByteArray())
}
}
override fun valueEquals(another: Message): Boolean {
if (another !is MessageChain) {
return false
}
return this.list == another.list
}
}

View File

@ -1,19 +0,0 @@
package net.mamoe.mirai.message.defaults;
import net.mamoe.mirai.message.Message;
/**
* @author Him188moe
*/
public final class PlainText extends Message {
private final String text;
public PlainText(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}

View File

@ -0,0 +1,34 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.network.packet.writeVarByteArray
import net.mamoe.mirai.network.packet.writeVarString
import net.mamoe.mirai.utils.lazyEncode
/**
* @author Him188moe
*/
class PlainText(private val text: String) : Message() {
override val type: Int = MessageId.TEXT
override fun toStringImpl(): String {
return text
}
override fun toByteArray(): ByteArray = lazyEncode { section ->
section.writeByte(this.type)
section.writeVarByteArray(lazyEncode { child ->
child.writeByte(0x01)
child.writeVarString(this.text)
})
}
override fun valueEquals(another: Message): Boolean {
if (another !is PlainText) {
return false
}
return this.text == another.text
}
}

View File

@ -10,10 +10,10 @@ import java.util.stream.Collectors
object Protocol {
val SERVER_IP: List<String> = object : ArrayList<String>() {
init {
add("183.60.56.29")
//add("183.60.56.29")
arrayOf(
"sz3.tencent.com",
//"sz3.tencent.com",
"sz4.tencent.com",
"sz5.tencent.com",
"sz6.tencent.com",
@ -64,6 +64,11 @@ object Protocol {
*/
const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"
/**
* 发送/接受消息中的一个const
*/
const val friendMessageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
@ExperimentalUnsignedTypes

View File

@ -1,23 +1,23 @@
@file:JvmMultifileClass
@file:JvmName("RobotNetworkHandler")
@file:JvmName("BotNetworkHandler")
package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
import net.mamoe.mirai.MiraiServer
import net.mamoe.mirai.Robot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
import net.mamoe.mirai.event.events.network.PacketSentEvent
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
import net.mamoe.mirai.event.events.robot.RobotLoginSucceedEvent
import net.mamoe.mirai.event.hookWhile
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.network.RobotNetworkHandler.*
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.BotNetworkHandler.*
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.packet.action.*
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.*
@ -29,15 +29,16 @@ import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import javax.imageio.ImageIO
import kotlin.reflect.KClass
/**
* Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收.
* [RobotNetworkHandler] 是全程异步和线程安全的.
* [BotNetworkHandler] 是全程异步和线程安全的.
*
* [RobotNetworkHandler] 2 个模块构成:
* [BotNetworkHandler] 2 个模块构成:
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
*
@ -47,12 +48,12 @@ import kotlin.reflect.KClass
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
*
* A RobotNetworkHandler is used to connect with Tencent servers.
* A BotNetworkHandler is used to connect with Tencent servers.
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
class RobotNetworkHandler(private val robot: Robot) : Closeable {
class BotNetworkHandler(private val bot: Bot) : Closeable {
private val socketHandler: SocketHandler = SocketHandler()
val debugHandler = DebugHandler()
@ -85,27 +86,26 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
//private | internal
internal fun tryLogin(): CompletableFuture<LoginState> = this.tryLogin(300)//登录回复非常快, 没必要等太久.
/**
* 仅当 [LoginState] [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCEED] 调用 [loginHandler]
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
*
* @param touchingTimeoutMillis 连接每个服务器的 timeout
*/
internal fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> {
@JvmOverloads
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
val future = CompletableFuture<LoginState>()
fun login() {
this.socketHandler.close()
val ip = ipQueue.poll()
if (ip == null) {
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
return
}
this@RobotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state ->
this@BotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
login()
} else {
@ -122,8 +122,17 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/
@ExperimentalUnsignedTypes
internal fun distributePacket(packet: ServerPacket) {
try {
packet.decode()
if (ServerPacketReceivedEvent(packet).broadcast().isCancelled) {
} catch (e: java.lang.Exception) {
e.printStackTrace()
bot.debug("Packet=$packet")
bot.debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
bot.debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
return
}
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
debugHandler.onPacketReceived(packet)
return
}
@ -145,6 +154,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
internal var loginFuture: CompletableFuture<LoginState>? = null
@Synchronized
private fun restartSocket() {
socket?.close()
socket = DatagramSocket(0)
@ -176,14 +186,14 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
* Start network and touch the server
*/
internal fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
MiraiLogger.info("Connecting server: $serverAddress")
bot.info("Connecting server: $serverAddress")
this.loginFuture = CompletableFuture()
socketHandler.serverIP = serverAddress
sendPacket(ClientTouchPacket(robot.account.qqNumber, socketHandler.serverIP))
waitForPacket(ServerTouchResponsePacket::class, timeoutMillis) {
waitForPacket(ServerPacket::class, timeoutMillis) {
loginFuture!!.complete(LoginState.TIMEOUT)
}
sendPacket(ClientTouchPacket(bot.account.qqNumber, socketHandler.serverIP))
return this.loginFuture!!
}
@ -191,6 +201,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
/**
* Not async
*/
@Synchronized
@ExperimentalUnsignedTypes
internal fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" }
@ -201,25 +212,25 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
try {
packet.encodePacket()
if (BeforePacketSendEvent(packet).broadcast().isCancelled) {
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
return
}
val data = packet.toByteArray()
socket!!.send(DatagramPacket(data, data.size))
MiraiLogger info "Packet sent: $packet"
bot cyanL "Packet sent: $packet"
PacketSentEvent(packet).broadcast()
PacketSentEvent(bot, packet).broadcast()
} catch (e: Throwable) {
e.printStackTrace()
}
}
@Suppress("UNCHECKED_CAST")
private fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
internal fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
var got = false
ServerPacketReceivedEvent::class.hookWhile {
if (packetClass.isInstance(it.packet)) {
if (packetClass.isInstance(it.packet) && it.bot == bot) {
got = true
true
} else {
@ -227,6 +238,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
}
}
MiraiThreadPool.getInstance().submit {
val startingTime = System.currentTimeMillis()
while (!got) {
@ -248,6 +260,10 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
this.loginFuture = null
}
}
fun isClosed(): Boolean {
return this.socket?.isClosed ?: true
}
}
@ -266,9 +282,11 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/
inner class DebugHandler : PacketHandler() {
override fun onPacketReceived(packet: ServerPacket) {
MiraiLogger info "Packet received: $packet"
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
bot notice "Packet received: $packet"
}
if (packet is ServerEventPacket) {
sendPacket(ClientMessageResponsePacket(robot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
sendPacket(ClientMessageResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
}
}
}
@ -281,7 +299,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
private lateinit var token0825: ByteArray
private var loginTime: Int = 0
private lateinit var loginIP: String
private var tgtgtKey: ByteArray? = null
private var tgtgtKey: ByteArray = getRandomByteArray(16)
private var tlv0105: ByteArray = lazyEncode {
it.writeHex("01 05 00 30")
@ -296,8 +314,8 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/
private lateinit var sessionResponseDecryptionKey: ByteArray
private var verificationCodeCacheId: Int = 0
private var verificationCodeCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var captchaSectionId: Int = 1
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var heartbeatFuture: ScheduledFuture<*>? = null
@ -309,13 +327,12 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
if (packet.serverIP != null) {//redirection
socketHandler.serverIP = packet.serverIP!!
//connect(packet.serverIP!!)
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, robot.account.qqNumber))
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
} else {//password submission
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
this.tgtgtKey = packet.tgtgtKey
sendPacket(ClientPasswordSubmissionPacket(robot.account.qqNumber, robot.account.password, packet.loginTime, packet.loginIP, packet.tgtgtKey, packet.token0825))
sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
}
}
@ -324,136 +341,117 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
return
}
is ServerLoginResponseVerificationCodeInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.verificationCodeCache = packet.verifyCodePart1
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
this.verificationCodeCacheId = 1
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, robot.account.qqNumber, this.token0825, this.verificationCodeCacheId, this.token00BA))
}
}
is ServerVerificationCodeCorrectPacket -> {
this.tgtgtKey = getRandomByteArray(16)
this.token00BA = packet.token00BA
sendPacket(ClientLoginResendPacket3105(robot.account.qqNumber, robot.account.password, this.loginTime, this.loginIP, this.tgtgtKey!!, this.token0825, this.token00BA))
sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
this.captchaSectionId = 1
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
}
}
is ServerVerificationCodeTransmissionPacket -> {
if (packet is ServerVerificationCodeWrongPacket) {
this.verificationCodeCacheId = 0
this.verificationCodeCache = byteArrayOf()
bot error "验证码错误, 请重新输入"
captchaSectionId = 1
this.captchaCache = byteArrayOf()
}
this.verificationCodeCacheId++
this.verificationCodeCache = this.verificationCodeCache!! + packet.verificationCodePartN
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
this.token00BA = packet.token00BA
if (packet.transmissionCompleted) {
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.verificationCodeCache!!)
println(CharImageUtil.createCharImg(ImageIO.read(this.verificationCodeCache!!.inputStream())))
println("需要验证码登录")
println("若看不清请查根目录下 VerificationCode.png")
println("若要更换验证码, 请直接回车")
bot notice (CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
bot notice ("需要验证码登录, 验证码为 4 字母")
try {
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!)
bot notice ("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
} catch (e: Exception) {
bot notice "无法写出验证码文件, 请尝试查看以上字符图片"
}
bot notice ("若要更换验证码, 请直接回车")
val code = Scanner(System.`in`).nextLine()
if (code.isEmpty()) {
sendPacket(ClientVerificationCodeRefreshPacket(robot.account.qqNumber, token0825, packet.verificationSessionId + 1))
if (code.isEmpty() || code.length != 4) {
this.captchaCache = byteArrayOf()
this.captchaSectionId = 1
sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
} else {
sendPacket(ClientVerificationCodeSubmitPacket(robot.account.qqNumber, token0825, packet.verificationSessionId + 1, code, packet.verificationToken))
sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
}
} else {
sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.verificationSessionId + 1, robot.account.qqNumber, this.token0825, this.verificationCodeCacheId, this.token00BA))
sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
}
}
is ServerLoginResponseSuccessPacket -> {
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
sendPacket(ClientSessionRequestPacket(robot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
is ServerLoginResponseResendPacket -> {
is ServerLoginResponseKeyExchangePacket -> {
//if (packet.tokenUnknown != null) {
//this.token00BA = packet.token00BA!!
//println("token00BA changed!!! to " + token00BA.toUByteArray())
//}
if (packet.flag == ServerLoginResponseResendPacket.Flag.`08 36 31 03`) {
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
this.tgtgtKey = packet.tgtgtKey
sendPacket(ClientLoginResendPacket3104(
robot.account.qqNumber,
robot.account.password,
this.loginTime,
this.loginIP,
this.tgtgtKey!!,
this.token0825,
when (packet.tokenUnknown != null) {
true -> packet.tokenUnknown!!
false -> this.token00BA
},
packet._0836_tlv0006_encr
))
sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: this.token00BA, packet.tlv0006))
} else {
sendPacket(ClientLoginResendPacket3106(
robot.account.qqNumber,
robot.account.password,
this.loginTime,
this.loginIP,
this.tgtgtKey!!,
this.token0825,
when (packet.tokenUnknown != null) {
true -> packet.tokenUnknown!!
false -> this.token00BA
},
packet._0836_tlv0006_encr
))
sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
}
}
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
sendPacket(ClientHeartbeatPacket(robot.account.qqNumber, sessionKey))
sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}, 90000, 90000, TimeUnit.MILLISECONDS)
RobotLoginSucceedEvent(robot).broadcast()
BotLoginSucceedEvent(bot).broadcast()
//登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({
(packetHandlers[MessageHandler::class] as MessageHandler).ignoreMessage = false
messageHandler.ignoreMessage = false
}, 2, TimeUnit.SECONDS)
this.tlv0105 = packet.tlv0105
sendPacket(ClientChangeOnlineStatusPacket(robot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
}
is ServerLoginSuccessPacket -> {
socketHandler.loginFuture!!.complete(LoginState.SUCCEED)
sendPacket(ClientSKeyRequestPacket(robot.account.qqNumber, sessionKey))
socketHandler.loginFuture!!.complete(LoginState.SUCCESS)
sendPacket(ClientSKeyRequestPacket(bot.account.qqNumber, sessionKey))
}
is ServerSKeyResponsePacket -> {
val actionHandler = packetHandlers[ActionHandler::class] as ActionHandler
actionHandler.sKey = packet.sKey
actionHandler.cookies = "uin=o" + robot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
actionHandler.cookies = "uin=o" + bot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
sendPacket(ClientSKeyRefreshmentRequestPacket(robot.account.qqNumber, sessionKey))
sendPacket(ClientSKeyRefreshmentRequestPacket(bot.account.qqNumber, sessionKey))
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
actionHandler.gtk = getGTK(actionHandler.sKey)
sendPacket(ClientAccountInfoRequestPacket(robot.account.qqNumber, sessionKey))
sendPacket(ClientAccountInfoRequestPacket(bot.account.qqNumber, sessionKey))
}
is ServerEventPacket.Raw -> distributePacket(packet.distribute())
is ServerVerificationCodePacket.Encrypted -> distributePacket(packet.decrypt())
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> distributePacket(packet.decrypt())
is ServerLoginResponseResendPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey!!))
is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey!!))
is ServerLoginResponseKeyExchangePacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
is ServerSessionKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> distributePacket(packet.decrypt())
is ServerSKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
@ -473,8 +471,7 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
}
override fun close() {
this.verificationCodeCache = null
this.tgtgtKey = null
this.captchaCache = null
this.heartbeatFuture?.cancel(true)
this.sKeyRefresherFuture?.cancel(true)
@ -490,6 +487,22 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
inner class MessageHandler : PacketHandler() {
internal var ignoreMessage: Boolean = false
init {
//todo for test
FriendMessageEvent::class.hookWhile {
if (socketHandler.isClosed()) {
return@hookWhile false
}
if (it.message() valueEquals "你好") {
it.qq.sendMessage("你好!")
} else if (it.message().toString().startsWith("复读")) {
it.qq.sendMessage(it.message())
}
return@hookWhile true
}
}
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
@ -501,15 +514,18 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
return
}
FriendMessageEvent(robot, robot.contacts.getQQ(packet.qq), packet.message)
FriendMessageEvent(bot, bot.contacts.getQQ(packet.qq), packet.message).broadcast()
}
is ServerGroupMessageEventPacket -> {
//todo message chain
//GroupMessageEvent(this.robot, robot.contacts.getGroupByNumber(packet.groupNumber), robot.contacts.getQQ(packet.qq), packet.message)
//GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message)
}
is UnknownServerEventPacket -> {
//todo
}
is UnknownServerEventPacket,
is ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> {
//ignored
@ -520,14 +536,13 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
}
}
fun sendFriendMessage(qq: QQ, message: Message) {
TODO()
//sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
fun sendFriendMessage(qq: QQ, message: MessageChain) {
sendPacket(ClientSendFriendMessagePacket(bot.account.qqNumber, qq.number, sessionKey, message))
}
fun sendGroupMessage(group: Group, message: Message): Unit {
TODO()
//sendPacket(ClientSendGroupMessagePacket(group.groupId, robot.account.qqNumber, sessionKey, message))
//sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message))
}
}
@ -537,15 +552,94 @@ class RobotNetworkHandler(private val robot: Robot) : Closeable {
*/
inner class ActionHandler : PacketHandler() {
internal lateinit var cookies: String
internal lateinit var sKey: String
internal var sKey: String = ""
set(value) {
field = value
gtk = getGTK(value)
}
internal var gtk: Int = 0
override fun onPacketReceived(packet: ServerPacket) {
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
this.addFriendSessions.forEach {
it.onPacketReceived(packet)
}
}
else -> {
}
}
}
fun addFriend(qqNumber: Long, message: Supplier<String>) {
addFriend(qqNumber, lazy { message.get() })
}
@JvmSynthetic
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message)
addFriendSessions.add(session)
session.sendAddRequest();
return future
}
override fun close() {
}
private inner class AddFriendSession(
private val qq: Long,
private val future: CompletableFuture<AddFriendResult>,
private val message: Lazy<String>
) : Closeable {
lateinit var id: ByteArray
fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
return
}
when (packet.state) {
ServerCanAddFriendResponsePacket.State.FAILED -> {
future.complete(AddFriendResult.FAILED)
close()
}
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
future.complete(AddFriendResult.ALREADY_ADDED)
close()
}
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
sendPacket(ClientAddFriendPacket(bot.account.qqNumber, qq, sessionKey))
}
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
}
}
}
}
}
fun sendAddRequest() {
sendPacket(ClientCanAddFriendPacket(bot.account.qqNumber, qq, sessionKey).also { this.id = it.packetIdLast })
}
override fun close() {
addFriendSessions.remove(this)
}
}
}
}

View File

@ -2,6 +2,7 @@ package net.mamoe.mirai.network.packet
import lombok.Getter
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.utils.*
import java.io.DataOutputStream
import java.io.IOException
@ -20,7 +21,7 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
init {
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
idHex = annotation.value
idHex = annotation.value.trim()
try {
this.writeHex(Protocol.head)
@ -29,7 +30,6 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
} catch (e: IOException) {
throw RuntimeException(e)
}
}
@Throws(IOException::class)
@ -60,10 +60,20 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
return toByteArray()
}
open fun getFixedId(): String = when (this.idHex.length) {
0 -> "__ __ __ __"
2 -> this.idHex + " __ __ __"
5 -> this.idHex + " __ __"
7 -> this.idHex + " __"
else -> this.idHex
}
override fun toString(): String {
return this.javaClass.simpleName + this.getAllDeclaredFields().joinToString(", ", "{", "}") {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "idByteArray" || it.name == "encoded" }.joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->
when (value) {
null -> null
is ByteArray -> value.toUHexString()
is UByteArray -> value.toUHexString()
else -> value.toString()
@ -144,12 +154,6 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo
}
}
/*
@ExperimentalUnsignedTypes
fun main() {
println(lazyEncode { it.writeTLV0006(1994701021, "D1 A5 C8 BB E1 Q3 CC DD", 131513, "123.123.123.123", "AA BB CC DD EE FF AA BB CC".hexToBytes()) }.toUByteArray().toUHexString())
}*/
@ExperimentalUnsignedTypes
@TestedSuccessfully
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16))
@ -166,11 +170,10 @@ fun DataOutputStream.writeCRC32(key: ByteArray) {
@ExperimentalUnsignedTypes
@TestedSuccessfully
fun DataOutputStream.writeDeviceName(random: Boolean = false) {
val deviceName: String
if (random) {
deviceName = String(getRandomByteArray(10))
val deviceName: String = if (random) {
String(getRandomByteArray(10))
} else {
deviceName = InetAddress.getLocalHost().hostName
InetAddress.getLocalHost().hostName
}
this.writeShort(deviceName.length + 2)
this.writeShort(deviceName.length)
@ -209,7 +212,7 @@ fun Int.toLByteArray(): ByteArray = byteArrayOf(
)
@ExperimentalUnsignedTypes
fun Int.toHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator)
internal fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
@ -226,7 +229,7 @@ fun DataOutputStream.writeZero(count: Int) {
@Throws(IOException::class)
fun DataOutputStream.writeRandom(length: Int) {
repeat(length) {
this.writeByte((Math.random() * 255).toInt().toByte().toInt())
this.writeByte((Math.random() * 255).toInt())
}
}
@ -241,3 +244,12 @@ fun DataOutputStream.writeQQ(qq: Long) {
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
}
fun DataOutputStream.writeVarByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size)
this.write(byteArray)
}
fun DataOutputStream.writeVarString(str: String) {
this.writeVarByteArray(str.toByteArray())
}

View File

@ -42,7 +42,7 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket {
this.input goto 14
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
return ServerAccountInfoResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream());
return ServerAccountInfoResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream()).setId(this.idHex)
}
}
}

View File

@ -1,8 +0,0 @@
package net.mamoe.mirai.network.packet;
/**
* @author Him188moe
*/
public interface Packet {
}

View File

@ -0,0 +1,28 @@
package net.mamoe.mirai.network.packet
/**
* @author Him188moe
*/
interface Packet {
}
object PacketNameFormatter {
@JvmSynthetic
private var longestNameLength: Int = 43
@JvmSynthetic
fun adjustName(name: String): String {
if (name.length > longestNameLength) {
longestNameLength = name.length
return name
}
return StringBuilder().apply {
repeat(longestNameLength - name.length) {
this.append(" ")
}
}.toString() + name
}
}

View File

@ -58,7 +58,7 @@ class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket {
this.input goto 14
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
return ServerSKeyResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream());
return ServerSKeyResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream()).setId(this.idHex)
}
}
}

View File

@ -1,9 +1,14 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.Face
import net.mamoe.mirai.message.defaults.Image
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
@ -39,12 +44,12 @@ open class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, va
//"02 10", "00 12" -> ServerUnknownEventPacket(this.input, packetId, eventIdentity)
else -> UnknownServerEventPacket(this.input, packetId, eventIdentity)
}
}.setId(this.idHex)
}
@PacketId("00 17")
class Encrypted(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey), packetId)
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey), packetId).setId(this.idHex)
}
}
}
@ -103,15 +108,15 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
//println(this.input.goto(110 + fontLength).readNBytesAt(2).toUHexString())//always 00 00
messageType = when (val id = this.input.goto(110 + fontLength + 2).readByte().toInt()) {
19 -> MessageType.NORMAL
14 -> MessageType.XML
6 -> MessageType.AT
0x13 -> MessageType.NORMAL
0xE -> MessageType.XML
0x06 -> MessageType.AT
1 -> MessageType.PLAIN_TEXT
2 -> MessageType.FACE
3 -> MessageType.IMAGE
25 -> MessageType.ANONYMOUS
0x01 -> MessageType.PLAIN_TEXT
0x02 -> MessageType.FACE
0x03 -> MessageType.IMAGE
0x19 -> MessageType.ANONYMOUS
else -> {
MiraiLogger debug ("ServerGroupMessageEventPacket id=$id")
@ -185,23 +190,102 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
@ExperimentalUnsignedTypes
override fun decode() {
//start at Sep1.0:27
input.goto(0)
println()
println(input.readAllBytes().toUHexString())
input.goto(0)
qq = input.readIntAt(0).toLong()
val msgLength = input.readShortAt(22)
val fontLength = input.readShortAt(93 + msgLength)
val offset = msgLength + fontLength
qq = input.readUIntAt(0).toLong()
val l1 = input.readShortAt(22)
input.goto(93 + l1)
input.readVarByteArray()//font
input.skip(2)//2个0x00
message = input.readSections()
println(message.toObjectString())
/*
val offset = unknownLength0 + fontLength//57
message = MessageChain(PlainText(let {
val offset2 = input.readShortAt(101 + offset)
input.goto(103 + offset).readVarString(offset2.toInt())
}))
val length = input.readShortAt(101 + offset)//
input.goto(103 + offset).readString(length.toInt())
}))*/
}
private fun DataInputStream.readSection(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
this.skip(1)//message和face是 0x01, image是0x06
return when (messageType) {
0x01 -> PlainText(readVarString())
0x02 -> {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
val id1 = FaceID.ofId(readVarNumber().toInt())//可能这个是id, 也可能下面那个
this.skip(this.readByte().toLong())
this.readVarNumber()//某id?
return Face(id1)
}
0x06 -> {
this.skip(sectionLength - 37 - 1)
val imageId = String(this.readNBytes(36))
this.skip(1)//0x41
return Image(imageId)
}
else -> null
}
}
private fun DataInputStream.readSections(): MessageChain {
val chain = MessageChain()
var got: Message? = null
do {
if (got != null) {
chain.concat(got)
}
got = this.readSection()
} while (got != null)
return chain
}
}
fun main() {
println(String("16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 31 37 36 32 65 42 39 45 32 37 32 31 43 39 36 44 37 39 41 38 32 31 36 45 30 41 44 34 30 42 35 39 35 39 31 38 36 2E 6A 70 67 66 2F 65 64 33 39 30 66 38 34 2D 34 66 38 37 2D 34 36 64 63 2D 62 33 38 35 2D 34 35 35 36 62 35 31 30 61 61 35 33 41".replace(" ", " ").hexToBytes()))
println(".jpg".toByteArray().size)
}
/*
牛逼 (10404
3E 03 3F A2 8F 00 1A E5 00 0B 53 3B 64 6B 91 17 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 40 5D 73 AF A1 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 AF A1 1F EE 24 55 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼 (10404
3E 03 3F A2 8F 00 1A E5 00 00 86 F3 09 18 83 47 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 41 5D 73 B3 21 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 B3 20 94 B0 82 BC 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼 (19947
76 E4 B8 DD 8F 00 1A E5 00 0D A2 8A 0A 65 7C D0 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 76 E4 B8 DD 8F 00 1A E5 4E 35 88 98 FE 64 7C E9 33 F7 2F B1 32 5D 5F A9 00 0B 77 FC 5D 73 B4 38 02 5B 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 B4 38 A6 60 C7 9A 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 0E 00 07 01 00 04 00 00 00 00
牛逼 (jiahua
B1 89 BE 09 8F 00 1A E5 00 0D EB CB 09 90 BA CF 1F 40 00 A6 00 00 00 20 00 05 00 02 00 01 00 06 00 04 00 01 05 0F 00 09 00 06 03 E9 20 02 E5 B3 00 0A 00 04 01 00 00 00 25 15 B1 89 BE 09 8F 00 1A E5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0B 77 A3 5D 73 B4 7D 00 00 5D 73 B4 7D 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 73 B4 7D 0E A3 93 E3 00 00 00 00 09 00 86 00 00 09 48 65 6C 76 65 74 69 63 61 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 0E 01 00 04 00 00 00 00 0A 00 04 00 00 00 00 19 00 1C 01 00 19 AA 02 16 08 00 88 01 00 9A 01 0E 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼 (jiahua
B1 89 BE 09 8F 00 1A E5 00 0B 03 A2 09 90 BB 7A 1F 40 00 A6 00 00 00 20 00 05 00 02 00 01 00 06 00 04 00 01 05 0F 00 09 00 06 03 E9 20 02 E5 B3 00 0A 00 04 01 00 00 00 25 15 B1 89 BE 09 8F 00 1A E5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0B 77 A5 5D 73 B6 33 00 00 5D 73 B6 33 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 73 B6 33 22 DE A7 56 00 00 00 00 09 00 86 00 00 09 48 65 6C 76 65 74 69 63 61 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 0E 01 00 04 00 00 00 00 0A 00 04 00 00 00 00 19 00 1C 01 00 19 AA 02 16 08 00 88 01 00 9A 01 0E 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼[EMOJI表情1]牛逼 (10404
3E 03 3F A2 8F 00 1A E5 00 0B 59 A3 64 6B 91 17 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 44 5D 73 BA D9 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 BA D9 12 C7 FC CD 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 02 00 14 01 00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼[EMOJI表情2]牛逼 (10404
3E 03 3F A2 8F 00 1A E5 00 0D 4D 4A 09 18 83 47 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 45 5D 73 BF A0 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 BF A0 68 31 43 A2 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 02 00 14 01 00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
牛逼[EMOJI表情1]牛逼 (10404
3E 03 3F A2 8F 00 1A E5 00 02 3C 8E 64 6B 91 17 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 47 5D 73 C3 76 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 73 C3 75 41 3B 97 72 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 0F 01 00 0C E7 89 9B E9 80 BC E7 89 9B E9 80 BC 02 00 14 01 00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
[图片] (10404
3E 03 3F A2 8F 00 1A E5 00 0E 02 CF 64 6B A0 0C 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 8F 00 1A E5 3B DF D8 CE 2B 2E 96 D0 12 CF 0D 44 CF C9 22 A0 00 0B 32 4B 5D 73 D1 1C 01 1D 00 00 00 00 01 00 00 00 0C 4D 53 47 00 00 00 00 00 5D 73 D1 1C F5 78 37 16 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 06 00 F0 02 00 1B 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 03 00 04 00 00 06 E2 04 00 25 2F 65 64 33 39 30 66 38 34 2D 34 66 38 37 2D 34 36 64 63 2D 62 33 38 35 2D 34 35 35 36 62 35 31 30 61 61 35 33 14 00 04 03 00 00 00 18 00 25 2F 65 64 33 39 30 66 38 34 2D 34 66 38 37 2D 34 36 64 63 2D 62 33 38 35 2D 34 35 35 36 62 35 31 30 61 61 35 33 19 00 04 00 00 00 38 1A 00 04 00 00 00 34 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 31 37 36 32 65 42 39 45 32 37 32 31 43 39 36 44 37 39 41 38 32 31 36 45 30 41 44 34 30 42 35 39 35 39 31 38 36 2E 6A 70 67 66 2F 65 64 33 39 30 66 38 34 2D 34 66 38 37 2D 34 36 64 63 2D 62 33 38 35 2D 34 35 35 36 62 35 31 30 61 61 35 33 41 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
*/
/**
* 告知服务器已经收到数据
@ -223,6 +307,10 @@ class ClientMessageResponsePacket(
it.write(eventIdentity)
}
}
override fun getFixedId(): String {
return packetIdFromServer.toUHexString()
}
}
/*
@ -253,7 +341,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
//.gif
}else {
val offset2 = input.readShortAt(101 + offset)
input.goto(103 + offset).readVarString(offset2.toInt())
input.goto(103 + offset).readString(offset2.toInt())
}
}
}

View File

@ -1,5 +1,7 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.packet.login.*
@ -10,6 +12,32 @@ import java.io.DataInputStream
* @author Him188moe
*/
abstract class ServerPacket(val input: DataInputStream) : Packet {
var idHex: String
var idByteArray: ByteArray//fixed 4 size
var encoded: Boolean = false
init {
idHex = try {
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
annotation.value.trim()
} catch (e: NullPointerException) {
""
}
idByteArray = if (idHex.isEmpty()) {
byteArrayOf(0, 0, 0, 0)
} else {
idHex.hexToBytes()
}
}
fun <P : ServerPacket> P.setId(idHex: String): P {
this.idHex = idHex
return this
}
open fun decode() {
@ -19,34 +47,32 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
@ExperimentalUnsignedTypes
fun ofByteArray(bytes: ByteArray): ServerPacket {
//println("Raw received: ${bytes.toUByteArray().toUHexString()}")
val stream = bytes.dataInputStream()
stream.skip(3)
return when (val idHex = stream.readInt().toHexString(" ")) {
val idHex = stream.readInt().toUHexString(" ")
return when (idHex) {
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, stream)
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, stream)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
when (bytes.size) {
271, 207 -> return ServerLoginResponseResendPacket.Encrypted(stream, when (idHex) {
"08 36 31 03" -> ServerLoginResponseResendPacket.Flag.`08 36 31 03`
else -> {
MiraiLogger debug ("ServerLoginResponseResendPacketEncrypted: flag=$idHex"); ServerLoginResponseResendPacket.Flag.OTHER
}
})
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(stream)
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(stream, when (idHex) {
"08 36 31 03" -> ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`
else -> ServerLoginResponseKeyExchangePacket.Flag.OTHER
}).apply { this.idHex = idHex }
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(stream).apply { this.idHex = idHex }
}
if (bytes.size > 700) {
return ServerLoginResponseSuccessPacket.Encrypted(stream)
return ServerLoginResponseSuccessPacket.Encrypted(stream).apply { this.idHex = idHex }
}
println(bytes.size)
return ServerLoginResponseFailedPacket(when (bytes.size) {
319, 135 -> LoginState.WRONG_PASSWORD
63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个也是被冻结
//135 -> LoginState.RETYPE_PASSWORD
279 -> LoginState.BLOCKED
263 -> LoginState.UNKNOWN_QQ_NUMBER
@ -57,10 +83,10 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
/*
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Illegal package data or Unknown error)")//包数据有误
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")
else -> throw IllegalArgumentException(bytes.size.toString())*/
}, stream)
}, stream).apply { this.idHex = idHex }
}
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(stream)
@ -83,15 +109,18 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
"00 CD" -> ServerSendFriendMessageResponsePacket(stream)
"00 02" -> ServerSendGroupMessageResponsePacket(stream)
"00 A7" -> ServerCanAddFriendResponsePacket(stream)
else -> throw IllegalArgumentException(idHex)
}
}
}.apply { this.idHex = idHex }
}
}
@ExperimentalUnsignedTypes
override fun toString(): String {
return this.javaClass.simpleName + this.getAllDeclaredFields().joinToString(", \n", "{", "}") {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "encoded" }.joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->
when (value) {
is ByteArray -> value.toUHexString()
@ -102,15 +131,48 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
}
}
open fun getFixedId(): String = getFixedId(this.idHex)
fun getFixedId(id: String): String = when (id.length) {
0 -> "__ __ __ __"
2 -> "$id __ __ __"
5 -> "$id __ __"
7 -> "$id __"
else -> id
}
fun decryptBy(key: ByteArray): DataInputStream {
input.goto(14)
return DataInputStream(TEA.decrypt(input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }, key).inputStream())
return decryptAsByteArray(key).dataInputStream()
}
@ExperimentalUnsignedTypes
fun decryptBy(keyHex: String): DataInputStream {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): DataInputStream {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream();
}
@ExperimentalUnsignedTypes
fun decryptBy(key1: String, key2: ByteArray): DataInputStream {
return this.decryptBy(key1.hexToBytes(), key2)
}
@ExperimentalUnsignedTypes
fun decryptBy(key1: ByteArray, key2: String): DataInputStream {
return this.decryptBy(key1, key2.hexToBytes())
}
@ExperimentalUnsignedTypes
fun decryptBy(keyHex1: String, keyHex2: String): DataInputStream {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
private fun decryptAsByteArray(key: ByteArray): ByteArray {
input.goto(14)
return TEA.decrypt(input.readAllBytes().cutTail(1), key)
}
}
@ -125,11 +187,15 @@ fun DataInputStream.readIP(): String {
return buff
}
fun DataInputStream.readShortVarString(): String {
return String(this.readNBytes(this.readShort().toInt()))
fun DataInputStream.readVarString(): String {
return String(this.readVarByteArray())
}
fun DataInputStream.readVarString(length: Int): String {
fun DataInputStream.readVarByteArray(): ByteArray {
return this.readNBytes(this.readShort().toInt())
}
fun DataInputStream.readString(length: Int): String {
return String(this.readNBytes(length))
}
@ -154,6 +220,17 @@ fun <N : Number> DataInputStream.readNBytes(length: N): ByteArray {
return this.readNBytes(length.toInt())
}
fun DataInputStream.readVarNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
4 -> this.readInt()
8 -> this.readLong()
else -> throw UnsupportedOperationException()
}
}
fun DataInputStream.readNBytesIn(range: IntRange): ByteArray {
this.goto(range.first)
return this.readNBytes(range.last - range.first + 1)
@ -164,6 +241,12 @@ fun <N : Number> DataInputStream.readIntAt(position: N): Int {
return this.readInt();
}
@ExperimentalUnsignedTypes
fun <N : Number> DataInputStream.readUIntAt(position: N): UInt {
this.goto(position)
return this.readNBytes(4).toUInt();
}
fun <N : Number> DataInputStream.readByteAt(position: N): Byte {
this.goto(position)
return this.readByte();

View File

@ -107,7 +107,7 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d
fun decrypt(sessionResponseDecryptionKey: ByteArray): ServerSessionKeyResponsePacket {
this.input goto 14
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
return ServerSessionKeyResponsePacket(TEA.decrypt(data, sessionResponseDecryptionKey).dataInputStream(), data.size)
return ServerSessionKeyResponsePacket(TEA.decrypt(data, sessionResponseDecryptionKey).dataInputStream(), data.size).setId(this.idHex)
}
}
}

View File

@ -1,7 +1,10 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.io.IOException
@ -13,13 +16,13 @@ import java.io.IOException
*
* @author Him188moe
*/
@PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
var serverIP: String? = null;
var serverIP: String? = null
var loginTime: Int = 0
lateinit var loginIP: String
lateinit var token0825: ByteArray
lateinit var tgtgtKey: ByteArray
enum class Type {
TYPE_08_25_31_01,
@ -40,7 +43,6 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
loginTime = input.readInt()
loginIP = input.readIP()
tgtgtKey = getRandomByteArray(16)
}
else -> {
@ -54,7 +56,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> Protocol.redirectionKey.hexToBytes()
Type.TYPE_08_25_31_01 -> Protocol.key0825.hexToBytes()
}))
})).setId(this.idHex)
}
}
@ -83,7 +85,6 @@ class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() {
this.writeIP(serverIp);
this.writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19")
this.writeHex(Protocol.publicKey)
println(super.toUByteArray().toUHexString())
return super.toByteArray()
}
}.toByteArray()))

View File

@ -1,10 +1,7 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
/**
@ -13,7 +10,7 @@ import java.io.DataInputStream
@ExperimentalUnsignedTypes
@PacketId("00 BA 31")
class ClientVerificationCodeTransmissionRequestPacket(
private val verificationSessionId: Int,
private val packetId: Int,
private val qq: Long,
private val token0825: ByteArray,
private val verificationSequence: Int,
@ -21,10 +18,10 @@ class ClientVerificationCodeTransmissionRequestPacket(
) : ClientPacket() {
@TestedSuccessfully
override fun encode() {
MiraiLogger debug "verificationSessionId=$verificationSessionId"
MiraiLogger debug "packetId=$packetId"
MiraiLogger debug "verificationSequence=$verificationSequence"
this.writeByte(verificationSessionId)//part of packet id
this.writeByte(packetId)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)
@ -52,14 +49,18 @@ class ClientVerificationCodeTransmissionRequestPacket(
@PacketId("00 BA 32")
@ExperimentalUnsignedTypes
class ClientVerificationCodeSubmitPacket(
private val packetIdLast: Int,
private val qq: Long,
private val token0825: ByteArray,
private val verificationSessionId: Int,
private val verificationCode: String,
private val verificationToken: ByteArray
) : ClientPacket() {
init {
require(verificationCode.length == 4) { "verificationCode.length must == 4" }
}
override fun encode() {
this.writeByte(verificationSessionId)//part of packet id
this.writeByte(packetIdLast)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)
@ -69,17 +70,51 @@ class ClientVerificationCodeSubmitPacket(
it.writeHex(Protocol.constantData2)
it.writeHex("01 00 38")
it.write(token0825)
it.writeHex("01 03 00 19")
it.writeHex("01 03")
it.writeShort(25)
it.writeHex(Protocol.publicKey)
it.writeHex("14 00 05 00 00 00 00 00 04")
it.write(verificationCode.substring(0..3).toByteArray())
it.writeByte(0x38)
it.write(verificationCode.toUpperCase().toByteArray())
it.writeHex("00 38")
it.write(verificationToken)
it.writeHex("00 10")
it.writeHex(Protocol.key00BAFix)
}
this.writeHex("")
}
override fun getFixedId(): String {
return this.idHex + " " + packetIdLast
}
}
@ExperimentalUnsignedTypes
fun main() {
val token0825 = "6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2".hexToBytes()
val verificationCode = "AAAA"
val verificationToken = "84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94".hexToBytes()
//00 02 00 00 08 04 01 E0 00 00 04 53 00 00 00 01 00 00 15 85 01 00 38 6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 14 00 05 00 00 00 00 00 04 58 51 4E 44 00 38 84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94 00 10 69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A
ByteArrayDataOutputStream().let {
it.writeHex("00 02 00 00 08 04 01 E0")
it.writeHex(Protocol.constantData2)
it.writeHex("01 00 38")
it.write(token0825)
it.writeHex("01 03")
it.writeShort(25)
it.writeHex(Protocol.publicKey)
it.writeHex("14 00 05 00 00 00 00 00 04")
it.write(verificationCode.substring(0..3).toByteArray())
it.writeHex("00 38")
it.write(verificationToken)
it.writeHex("00 10")
it.writeHex(Protocol.key00BAFix)
println(it.toByteArray().toUHexString())
}
}
@ -89,12 +124,12 @@ class ClientVerificationCodeSubmitPacket(
@PacketId("00 BA 31")
@ExperimentalUnsignedTypes
class ClientVerificationCodeRefreshPacket(
private val packetIdLast: Int,
private val qq: Long,
private val token0825: ByteArray,
private val verificationSessionId: Int
private val token0825: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeByte(verificationSessionId)//part of packet id
this.writeByte(packetIdLast)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)
@ -109,16 +144,18 @@ class ClientVerificationCodeRefreshPacket(
it.writeHex("13 00 05 00 00 00 00 00 00 00 00 10")
it.writeHex(Protocol.key00BAFix)
}
this.writeHex("")
}
override fun getFixedId(): String {
return this.idHex + " " + packetIdLast
}
}
/**
* 验证码输入错误
*/
class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId) {
}
@PacketId("00 BA 32")
class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId)
/**
* 服务器发送验证码图片文件一部分过来
@ -128,42 +165,47 @@ class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, p
@PacketId("00 BA 31")
open class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) {
lateinit var verificationCodePartN: ByteArray
lateinit var captchaSectionN: ByteArray
lateinit var verificationToken: ByteArray//56bytes
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
lateinit var token00BA: ByteArray//40 bytes
var verificationSessionId: Int = 0
var packetIdLast: Int = 0
@ExperimentalUnsignedTypes
override fun decode() {
this.verificationToken = this.input.readNBytesAt(10, 56)
val length = this.input.readShortAt(66)
this.verificationCodePartN = this.input.readNBytes(length)
this.captchaSectionN = this.input.readNBytes(length)
this.input.skip(1)
val byte = this.input.readByteAt(69 + length).toInt()
this.transmissionCompleted = byte == 0
this.token00BA = this.input.readNBytesAt(dataSize - 56 - 2, 40)
this.verificationSessionId = packetId[3].toInt()
this.packetIdLast = packetId[3].toInt()
}
override fun getFixedId(): String {
return this.idHex + " " + packetIdLast
}
}
/*
fun main() {
val data = "13 00 05 01 00 00 01 23 00 38 41 8A 4F 49 BF 68 90 72 9E F6 DB 2A 85 12 81 BF 95 0A 64 60 3C 1A 12 20 41 74 CA BC B8 C3 0D 48 28 7B F6 C1 D2 7D B3 3D E2 12 FA 64 58 9C 79 9A 8B 6A 58 14 6C 86 25 6B 02 BC F5 EE 11 59 90 F9 12 46 D1 B0 14 DF CE DB 34 CD 0E 4C 62 5B D7 7D D7 A2 F3 51 AB EB 8B E0 CE 5C 32 A6 8A D3 D6 1D 44 22 1D E8 38 E8 F5 03 E0 91 E9 56 FB 55 91 20 36 89 F7 10 B3 ED A9 D9 11 F7 2A AB 4D 0E 6D DE 8F A1 AE A5 6F D2 91 CE D3 58 D8 04 3E BF 2C D3 3E 13 12 08 18 A8 92 F2 0D 32 40 79 B6 07 40 83 F6 6A 28 BD 94 0C 31 EA E1 C4 A3 11 46 8D 48 C8 7A E2 4E 4F 20 24 EC 48 9A 2D 14 FE 66 90 ED 8E 8A 7F D7 6D 08 64 46 90 82 37 65 A2 0C 4D 64 DC 2F AC 1F D4 9F D7 54 61 34 34 35 2A B0 CE B1 64 62 24 6B A5 33 C4 B4 9C 26 10 B6 0E 0C DA EC 20 70 43 DC 3C FB A5 A9 E3 61 49 0D A2 0F C6 DC B1 20 A7 DE 42 83 9F 48 C2 C3 98 7D 6D 3C F3 53 86 58 D6 5A 4D 0A 52 A3 39 6A FE 41 A9 9C FB C8 7A 63 1A 40 06 58 EE D7 BA 62 29 CB 9A BC 72 6B D2 FD B9 3C 94 B4 42 BE F4 F8 BD F7 06 50 9E 64 E0 27 FD 62 53 5A AA 7A C9 5C DA B2 5E BC F2 40 05 98 D8 30 74 47 9A E3 BA 85 CE 5B CC DF 3F C7 1F 0D 9E B2 03 59 6B C1 4B 75 4E C5 F3 E9 E8 86 28 D6 FC A9 1C A3 25 DF 78 91 39 8B BB 66 B5 AA FF 53 D3 0B 7D 93 A8 EF 96 70 E9 3B D3 CB 66 99 B0 33 33 62 DB D3 44 02 77 3A 26 ED 2B ED 19 98 8A 25 39 7B 9B 0B D4 26 FA AC 77 83 76 DF 22 F3 EF 37 3C 16 A6 19 9F 73 65 C4 56 5F E6 D2 E3 A9 BC F5 1A 57 F5 D0 A0 09 CA 8A 70 F1 97 41 AD 36 C1 74 B2 9A F0 FE 6B C6 B5 86 1D C2 F6 7B BE 4B 2B A3 C0 B3 14 9C B3 9E D8 D9 C8 05 A3 55 02 92 34 2F C5 52 5C 44 E3 6B 31 B8 73 3F 34 21 5D 5D EF 7D 10 EF 19 F6 45 2E C2 EE 1F 47 81 7C 9B BA 99 5D 40 0F A3 05 12 00 B8 9E FC CA 7A 50 8F 90 1D F1 D4 27 6F F6 4B 41 D6 39 A1 3A 91 0C 7D 8B 17 0D D7 F6 42 EA D1 6D 55 F8 34 C2 A5 D2 F1 1E A5 C1 1A 95 A3 7C 95 E0 D4 E5 00 5F D3 D0 33 43 F6 FA 0E 26 D4 04 2B 9B 87 71 9E 77 4D 3F E7 DC 63 2A 72 47 38 94 58 57 DA 93 B4 A8 1D A0 9D 9F 35 68 54 A3 DE 50 74 87 38 CD 08 CE 35 BE E3 50 13 C1 8C F3 09 D7 BD 70 A1 A7 33 C6 A5 1E FC 17 9F 93 FA 77 2F EF EE 6D 89 B0 96 0F 49 30 53 81 BB 2A E7 5C CD BD 98 65 3D E1 75 E7 06 77 E3 F9 45 EF E0 52 48 75 2D 52 55 01 70 5D 11 C2 A7 1B CC 81 46 E9 B4 E1 E5 CB 3E 47 89 5F 3F 47 AA 3B 1D 7D 2A 30 FE 6F EC 03 59 BA F1 0E 09 7D 4F B5 9D AB FF DB F5 B4 26 1B 40 F5 BE FF 03 E7 64 A0 DE A1 0C 6B 62 39 1F 01 01 00 28 57 1B 29 6C A0 FD FB 82 34 7D 92 07 77 CF CE 7A C9 19 FA 46 9C 9B 0A 43 30 6F 1D EE D3 E0 46 A4 83 20 CD 0A 6C AF 1A 1E 00 10 E0 5A E1 E7 FE 45 53 0B 2B 70 68 40 CB C1 D6 7A".hexToBytes()
val data = "13 00 05 01 00 00 01 23 00 38 59 32 29 5A 3E 3D 2D FC F5 22 EB 9E 2D FB 9C 4F AA 06 C8 32 3D F0 3C 2C 2B BA 8D 05 C4 9B C1 74 3B 70 F1 99 90 BB 6E 3E 6F 74 48 97 D3 61 B7 04 C0 A3 F1 DF 40 A4 DC 2B 00 A2 01 2D BB BB E8 FE B8 AF B3 6F 39 7C EA E2 5B 91 BE DB 59 38 CF 58 BC F2 88 F1 09 CF 92 E9 F7 FB 13 76 C5 68 29 23 3F 8E 43 16 2E 50 D7 FA 4D C1 F7 67 EF 27 FB C6 F1 A7 25 A4 BC 45 39 3A EA B2 A5 38 02 FF 4B C9 FF EB BD 89 E5 5D B9 4A 2A BE 5F 52 F1 EB 09 29 CB 3E 66 CF EF 97 89 47 BB 6B E0 7B 4A 3E A1 BC 3F FB F2 0A 83 CB E3 EA B9 43 E1 26 88 03 0B A7 E0 B2 AD 7F 83 CC DA 74 85 83 72 08 EC D2 F9 95 05 15 05 96 F7 1C FF 00 82 C3 90 22 A4 BA 90 D5 00 00 00 00 49 45 4E 44 AE 42 60 82 03 00 00 28 EA 32 5A 85 C8 D2 73 B3 40 39 77 85 65 98 00 FE 03 A2 A5 95 B4 2F E6 79 7A DE 5A 03 10 C8 3D BF 6D 3D 8B 51 84 C2 6D 49 00 10 92 AA 69 FB C6 3D 60 5A 7A A4 AC 7A B0 71 00 36".hexToBytes()
ServerVerificationCodeTransmissionPacket(data.dataInputStream(), data.size, "00 BA 31 01".hexToBytes()).let {
it.decode()
println(it.toString())
}
}
}*/
/**
* 验证码正确
*
* @author Him188moe
*/
@PacketId("00 BA 32")
class ServerVerificationCodeCorrectPacket(input: DataInputStream) : ServerVerificationCodePacket(input) {
lateinit var token00BA: ByteArray//56 bytes
@ -177,20 +219,23 @@ class ServerVerificationCodeCorrectPacket(input: DataInputStream) : ServerVerifi
abstract class ServerVerificationCodePacket(input: DataInputStream) : ServerPacket(input) {
@PacketId("00 BA")
class Encrypted(input: DataInputStream, val idHex: String) : ServerPacket(input) {
class Encrypted(input: DataInputStream, private val id: String) : ServerPacket(input) {
@ExperimentalUnsignedTypes
fun decrypt(): ServerVerificationCodePacket {
this.input goto 14
val data = TEA.decrypt(this.input.readAllBytes().cutTail(1), Protocol.key00BA.hexToBytes())
if (idHex.startsWith("00 BA 32")) {
if (data.size == 95) {
ServerVerificationCodeCorrectPacket(data.dataInputStream())
} else {
return ServerVerificationCodeWrongPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4))
}
if (id.startsWith("00 BA 32")) {
return when (data.size) {
66,
95 -> ServerVerificationCodeCorrectPacket(data.dataInputStream())
//66 -> ServerVerificationCodeUnknownPacket(data.dataInputStream())
else -> return ServerVerificationCodeWrongPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4))
}.setId(this.idHex)
}
return ServerVerificationCodeTransmissionPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4))
return ServerVerificationCodeTransmissionPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4)).setId(this.idHex)
}
override fun getFixedId(): String = this.getFixedId(id)
}
}

View File

@ -0,0 +1,133 @@
package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.getRandomByteArray
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.util.*
/**
* 向服务器检查是否可添加某人为好友
*
* @author Him188moe
*/
@PacketId("00 A7")
@ExperimentalUnsignedTypes
class ClientCanAddFriendPacket(
val bot: Long,
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
this.writeQQ(bot)
this.writeHex(Protocol.fixVer2)
this.encryptAndWrite(sessionKey) {
it.writeQQ(qq)
}
}
}
@PacketId("00 A7")
class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(input) {
lateinit var state: State
enum class State {
ALREADY_ADDED,
REQUIRE_VERIFICATION,
NOT_REQUIRE_VERIFICATION,
FAILED,
}
@ExperimentalUnsignedTypes
override fun decode() {
val data = input.goto(0).readAllBytes()
if (data.size == 99) {
state = State.ALREADY_ADDED
return
}
state = when (data[data.size - 1].toUInt()) {
0u -> State.NOT_REQUIRE_VERIFICATION
1u -> State.REQUIRE_VERIFICATION
99u -> State.ALREADY_ADDED
3u, 4u -> State.FAILED
else -> throw IllegalArgumentException(Arrays.toString(data))
}
}
@PacketId("00 A7")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket {
return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
}
/**
* 请求添加好友
*/
@PacketId("00 AE")
@ExperimentalUnsignedTypes
class ClientAddFriendPacket(
val bot: Long,
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
this.writeQQ(bot)
this.writeHex(Protocol.fixVer2)
this.encryptAndWrite(sessionKey) {
it.writeHex("01 00 01")
it.writeQQ(qq)
}
}
}
class ServerAddFriendResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
class ServerAddGroupResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
/**
* 添加好友/群的回复
*/
open class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) {
class Raw(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
fun distribute(): ServerAddContactResponsePacket {
TODO()
}
class Encrypted(input: DataInputStream) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey))
}
}
}

View File

@ -0,0 +1,21 @@
package net.mamoe.mirai.network.packet.action
/**
* 添加好友结果
*/
enum class AddFriendResult {
/**
* 等待对方处理
*/
WAITING_FOR_AGREEMENT,
/**
* 和对方已经是好友了
*/
ALREADY_ADDED,
/**
* 对方设置为不添加好友等
*/
FAILED,
}

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.lazyEncode
@ -11,22 +12,22 @@ import java.io.DataInputStream
@PacketId("00 CD")
@ExperimentalUnsignedTypes
class ClientSendFriendMessagePacket(
private val robotQQ: Long,
private val botQQ: Long,
private val targetQQ: Long,
private val sessionKey: ByteArray,
private val message: String
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(robotQQ)
this.writeQQ(botQQ)
this.writeHex(Protocol.fixVer2)
this.encryptAndWrite(sessionKey) {
it.writeQQ(robotQQ)
it.writeQQ(botQQ)
it.writeQQ(targetQQ)
it.writeHex("00 00 00 08 00 01 00 04 00 00 00 00")
it.writeHex("37 0F")
it.writeQQ(robotQQ)
it.writeQQ(botQQ)
it.writeQQ(targetQQ)
it.write(md5(lazyEncode { md5Key -> md5Key.writeQQ(targetQQ); md5Key.write(sessionKey) }))
it.writeHex("00 0B")
@ -35,24 +36,21 @@ class ClientSendFriendMessagePacket(
it.writeHex("00 00 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00")
it.writeTime()
it.writeRandom(4)
it.writeHex("00 00 00 00 09 00 86 00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91")
it.writeHex("00 00 00 00 09 00 86")
it.writeHex(Protocol.friendMessageConst1)//... 85 E9 BB 91
it.writeZero(2)
if ("[face" in message
|| ".gif]" in message
|| ".jpg]" in message
|| ".png]" in message
) {
TODO("复合消息构建")
} else {
it.write(message.toByteArray())
/*
//Plain text
val bytes = message.toByteArray()
it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
it.writeShort(bytes.size)
it.write(bytes)
}
it.write(bytes)*/
}
}
}

View File

@ -12,13 +12,13 @@ import java.io.DataInputStream
@ExperimentalUnsignedTypes
class ClientSendGroupMessagePacket(
private val groupId: Long,//不是 number
private val robotQQ: Long,
private val botQQ: Long,
private val sessionKey: ByteArray,
private val message: String
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(robotQQ)
this.writeQQ(botQQ)
this.writeHex(Protocol.fixVer2)
this.encryptAndWrite(sessionKey) {
@ -30,7 +30,7 @@ class ClientSendGroupMessagePacket(
it.writeHex("00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
it.writeTime()
it.writeRandom(4)
it.writeHex("00 00 00 00 09 00 86 00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91")
it.writeHex("Protocol.messageConst1")
it.writeZero(2)
//messages

View File

@ -17,8 +17,10 @@ class ClientChangeOnlineStatusPacket(
private val loginStatus: ClientLoginStatus
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol.fixVer2)
this.encryptAndWrite(sessionKey) {

View File

@ -2,7 +2,10 @@ package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataOutputStream
/**
@ -32,15 +35,14 @@ class ClientPasswordSubmissionPacket(
this.encryptAndWrite(Protocol.shareKey.hexToBytes()) {
it.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825)
it.writePart2()
println(it.toByteArray().toUHexString())
}
}
}
@PacketId("08 36 31 04")
@ExperimentalUnsignedTypes
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv_0006_encr)
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
@PacketId("08 36 31 05")
@ExperimentalUnsignedTypes
@ -49,8 +51,8 @@ class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, lo
@PacketId("08 36 31 06")
@ExperimentalUnsignedTypes
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv_0006_encr)
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
@ExperimentalUnsignedTypes
open class ClientLoginResendPacket internal constructor(
@ -61,7 +63,7 @@ open class ClientLoginResendPacket internal constructor(
val tgtgtKey: ByteArray,
val token0825: ByteArray,
val token00BA: ByteArray,
val tlv_0006_encr: ByteArray? = null
val tlv0006: ByteArray? = null
) : ClientPacket() {
override fun encode() {
this.writeQQ(qq)
@ -72,7 +74,7 @@ open class ClientLoginResendPacket internal constructor(
this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
override fun toByteArray(): ByteArray {
this.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv_0006_encr)
this.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv0006)
this.writeHex("01 10") //tag
this.writeHex("00 3C")//length
@ -92,21 +94,21 @@ open class ClientLoginResendPacket internal constructor(
* @author Him188moe
*/
@ExperimentalUnsignedTypes
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv_0006_encr: ByteArray? = null) {
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
//this.writeInt(System.currentTimeMillis().toInt())
this.writeHex("01 12")//tag
this.writeHex("00 38")//length
this.write(token0825)//length
this.writeHex("03 0F")//tag
this.writeDeviceName(true)//todo 随机
this.writeDeviceName(true)
this.writeHex("00 05 00 06 00 02")
this.writeQQ(qq)
this.writeHex("00 06")//tag
this.writeHex("00 78")//length
if (tlv_0006_encr != null) {
this.write(tlv_0006_encr)
if (tlv0006 != null) {
this.write(tlv0006)
} else {
this.writeTLV0006(qq, password, loginTime, loginIP, tgtgtKey)
}

View File

@ -7,7 +7,7 @@ enum class LoginState {
/**
* 登录成功
*/
SUCCEED,
SUCCESS,
/**
* 密码错误

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.goto
@ -7,16 +8,18 @@ import net.mamoe.mirai.utils.TestedSuccessfully
import java.io.DataInputStream
/**
* 服务器进行加密后返回 tgtgtKey
*
* @author NaturalHG
*/
@PacketId("08 36 31 03")
class ServerLoginResponseResendPacket(input: DataInputStream, val flag: Flag) : ServerPacket(input) {
class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Flag) : ServerPacket(input) {
enum class Flag {
`08 36 31 03`,
OTHER,
}
lateinit var _0836_tlv0006_encr: ByteArray;//120bytes
lateinit var tlv0006: ByteArray;//120bytes
var tokenUnknown: ByteArray? = null
lateinit var tgtgtKey: ByteArray//16bytes
@ -26,7 +29,7 @@ class ServerLoginResponseResendPacket(input: DataInputStream, val flag: Flag) :
tgtgtKey = this.input.readNBytes(16)//22
//this.input.skip(2)//25
this.input.goto(25)
_0836_tlv0006_encr = this.input.readNBytes(120)
tlv0006 = this.input.readNBytes(120)
when (flag) {
Flag.`08 36 31 03` -> {
@ -36,14 +39,17 @@ class ServerLoginResponseResendPacket(input: DataInputStream, val flag: Flag) :
Flag.OTHER -> {
//do nothing in this packet.
//[this.token] will be set in [RobotNetworkHandler]
//[this.token] will be set in [BotNetworkHandler]
//token
}
}
}
class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) {
@ExperimentalUnsignedTypes
@TestedSuccessfully
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseResendPacket = ServerLoginResponseResendPacket(decryptBy(tgtgtKey), flag)
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseKeyExchangePacket {
return ServerLoginResponseKeyExchangePacket(this.decryptBy(Protocol.shareKey, tgtgtKey), flag).setId(this.idHex)
}
}
}

View File

@ -1,8 +1,10 @@
package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.network.packet.readNBytesAt
import net.mamoe.mirai.network.packet.readString
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
@ -41,7 +43,7 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
this.token88 = this.input.readNBytesAt(189 + msgLength, 136)
val nickLength = this.input.goto(624 + msgLength).readByte().toInt()
this.nickname = this.input.readVarString(nickLength)
this.nickname = this.input.readString(nickLength)
//this.age = this.input.goto(packetDataLength - 28).readShortAt()
@ -53,7 +55,7 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
@ExperimentalUnsignedTypes
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket {
input goto 14
return ServerLoginResponseSuccessPacket(TEA.decrypt(TEA.decrypt(input.readAllBytes().cutTail(1), Protocol.shareKey), tgtgtKey).dataInputStream());
return ServerLoginResponseSuccessPacket(this.decryptBy(Protocol.shareKey, tgtgtKey)).setId(this.idHex)
}
}

View File

@ -43,7 +43,7 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
this.input goto 14
val data = TEA.CRYPTOR_SHARE_KEY.decrypt(this.input.readAllBytes().cutTail(1));
return ServerLoginResponseVerificationCodeInitPacket(data.dataInputStream(), data.size)
return ServerLoginResponseVerificationCodeInitPacket(data.dataInputStream(), data.size).setId(this.idHex)
}
}
}

View File

@ -1,11 +1,11 @@
package net.mamoe.mirai.plugin;
import net.mamoe.mirai.Robot;
import net.mamoe.mirai.Bot;
/**
* 插件基类.
* <p>
* 插件属于整个 Mirai, 而不是属于单个 {@link Robot}.
* 插件属于整个 Mirai, 而不是属于单个 {@link Bot}.
*
* @see net.mamoe.mirai.event.MiraiEventManager
* @see net.mamoe.mirai.event.MiraiEventManagerKt

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot
import java.text.SimpleDateFormat
import java.util.*
@ -28,12 +29,42 @@ object MiraiLogger {
this.print(e.cause.toString())*/
}
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s : $value")
}
}
infix fun Bot.log(o: Any?) = info(o)
infix fun Bot.println(o: Any?) = info(o)
infix fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
infix fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
infix fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
infix fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
infix fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
infix fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
infix fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
@Synchronized
private fun print(bot: Bot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s #R${bot.id}: $value")
}
@Synchronized
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
kotlin.io.println("$color[Mirai] $s : $value")
}
fun Any.logInfo() = MiraiLogger.info(this)

View File

@ -6,11 +6,11 @@ import lombok.Data;
* @author Him188moe
*/
@Data
public final class RobotAccount {
public final class BotAccount {
public final long qqNumber;
public final String password;
public RobotAccount(long qqNumber, String password) {
public BotAccount(long qqNumber, String password) {
this.qqNumber = qqNumber;
this.password = password;
}

View File

@ -17,6 +17,7 @@ public final class TEA {
private static final long UINT32_MASK = 0xffffffffL;
private final long[] mKey;
private final Random mRandom;
private final byte[] key;
private byte[] mOutput;
private byte[] mInBlock;
private int mIndexPos;
@ -26,6 +27,7 @@ public final class TEA {
private boolean isFirstBlock;
public TEA(byte[] key) {
this.key = key;
mKey = new long[4];
for (int i = 0; i < 4; i++) {
mKey[i] = pack(key, i * 4, 4);
@ -250,6 +252,12 @@ public final class TEA {
}
public byte[] decrypt(byte[] ciphertext) {
try {
return decrypt(ciphertext, 0, ciphertext.length);
} catch (Exception e) {
System.out.println("Source: " + UtilsKt.toUHexString(ciphertext, " "));
System.out.println("Key: " + UtilsKt.toUHexString(this.key, " "));
throw e;
}
}
}

View File

@ -45,9 +45,6 @@ fun UByteArray.toUHexString(): String = this.toUHexString(" ")
@ExperimentalUnsignedTypes
fun Byte.toUHexString(): String = this.toUByte().toString(16)
/**
* firstly [Protocol.hexToUBytes], secondly [UByteArray.toByteArray]
*/
@ExperimentalUnsignedTypes
fun String.hexToBytes(): ByteArray = Protocol.hexToBytes(this)
@ -55,13 +52,11 @@ fun String.hexToBytes(): ByteArray = Protocol.hexToBytes(this)
fun String.hexToUBytes(): UByteArray = Protocol.hexToUBytes(this)
@ExperimentalUnsignedTypes
fun String.hexToShort(): Short = hexToBytes().let { ((it[1].toInt() shl 8) + it[0]).toShort() }
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
@ExperimentalUnsignedTypes
fun String.hexToInt(): Int = hexToBytes().let { ((it[0].toInt() shl 24) + (it[1].toInt() shl 16) + (it[2].toInt() shl 8) + it[3]) }
@ExperimentalUnsignedTypes
fun String.hexToByte(): Byte = hexToBytes()[0]
fun ByteArray.toUInt(): UInt =
this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream()) {
open fun toByteArray(): ByteArray = (out as ByteArrayOutputStream).toByteArray()
@ -101,8 +96,6 @@ internal fun getCrc32(key: ByteArray): Int = CRC32().let { it.update(key); it.va
* 相当于将这个类和它所有父类的 [Class.getDeclaredFields] 都合并成一个 [List] <br></br>
* 不会排除重名的字段. <br></br>
*
* @param clazz class
*
* @return field list
*/
@Throws(SecurityException::class)

View File

@ -1,8 +1,8 @@
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.mamoe.mirai.Robot
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.packet.login.LoginState
import net.mamoe.mirai.utils.RobotAccount
import net.mamoe.mirai.utils.BotAccount
import java.util.*
/**
@ -64,21 +64,21 @@ val qqList = "2535777366----abc123456\n" +
fun main() {
val goodRobotList = Collections.synchronizedList(mutableListOf<Robot>())
val goodBotList = Collections.synchronizedList(mutableListOf<Bot>())
qqList.split("\n").forEach {
GlobalScope.launch {
val strings = it.split("----")
val robot = Robot(RobotAccount(strings[0].toLong(), strings[1].let { password ->
val bot = Bot(BotAccount(strings[0].toLong(), strings[1].let { password ->
if (password.endsWith(".")) {
return@let password.substring(0, password.length - 1)
}
return@let password
}), listOf())
robot.network.tryLogin().whenComplete { state, _ ->
bot.network.tryLogin().whenComplete { state, _ ->
if (!(state == LoginState.BLOCKED || state == LoginState.DEVICE_LOCK || state == LoginState.WRONG_PASSWORD)) {
goodRobotList.add(robot)
goodBotList.add(bot)
}
}
}
@ -86,6 +86,5 @@ fun main() {
Thread.sleep(9 * 3000)
println(goodRobotList.joinToString("\n") { it.account.qqNumber.toString() + " " + it.account.password })
println(goodBotList.joinToString("\n") { it.account.qqNumber.toString() + " " + it.account.password })
}

View File

@ -50,10 +50,20 @@ public class HexComparator {
@SuppressWarnings({"unused", "NonAsciiCharacters"})
private static class TestConsts {
private static final String 牛逼 = UtilsKt.toUHexString("牛逼".getBytes(), " ");
private static final String _1994701021 = ClientPacketKt.toHexString(1994701021, " ");
private static final String _1040400290 = ClientPacketKt.toHexString(1040400290, " ");
private static final String _580266363 = ClientPacketKt.toHexString(580266363, " ");
private static final String NIU_BI = UtilsKt.toUHexString("牛逼".getBytes(), " ");
private static final String _1994701021 = ClientPacketKt.toUHexString(1994701021, " ");
private static final String _1040400290 = ClientPacketKt.toUHexString(1040400290, " ");
private static final String _580266363 = ClientPacketKt.toUHexString(580266363, " ");
private static final String _1040400290_ = "3E 03 3F A2";
private static final String _1994701021_ = "76 E4 B8 DD";
private static final String _jiahua_ = "B1 89 BE 09";
private static final String SINGLE_PLAIN_MESSAGE_HEAD = "00 00 01 00 09 01";
private static final String MESSAGE_TAIL_10404 = "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00".replace(" ", " ");
//private static final String MESSAGE_TAIL2_10404 ="".replace(" ", " ");
}
private final List<Match> matches = new LinkedList<>();
@ -88,7 +98,7 @@ public class HexComparator {
return new LinkedList<>();
}
return new LinkedList<>() {{
int index = 0;
int index = -1;
while ((index = hex.indexOf(constValue, index + 1)) != -1) {
add(new IntRange(index / 3, (index + constValue.length()) / 3));
}
@ -105,18 +115,20 @@ public class HexComparator {
}
private static void buildConstNameChain(int length, ConstMatcher constMatcher, StringBuilder constNameBuilder) {
//System.out.println(constMatcher.matches);
for (int i = 0; i < length; i++) {
constNameBuilder.append(" ");
String match = constMatcher.getMatchedConstName(i / 4);
if (match != null) {
int appendedNameLength = match.length();
constNameBuilder.append(match);
while (constMatcher.getMatchedConstName(i++ / 4) != null) {
if (appendedNameLength-- <= 0) {
while (match.equals(constMatcher.getMatchedConstName(i++ / 4))) {
if (appendedNameLength-- < 0) {
constNameBuilder.append(" ");
}
}
constNameBuilder.append(" ".repeat(match.length() % 4));
}
}
}
@ -281,10 +293,10 @@ public class HexComparator {
System.out.println(HexComparator.compare(
//mirai
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC 00 00 00 00 09 00 86 00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n"
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n"
,
//e
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC 00 00 00 00 09 00 86 00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65"
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65"
));

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mirai</artifactId>
<groupId>net.mamoe</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mirai-native</artifactId>
<version>1.0</version>
</project>

View File

@ -1,9 +0,0 @@
package net.mamoe.mirai.util;
/**
* @author Him188moe
*/
public final class TeaEncryption {
public static native int Decrypt();
}

39
mirai-ui/pom.xml Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>mirai-ui</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
</plugins>
</build>
</project>

142
pom.xml
View File

@ -11,7 +11,9 @@
<modules>
<module>mirai-core</module>
<module>mirai-native</module>
<module>mirai-ui</module>
<module>mirai-console</module>
<module>mirai-api</module>
</modules>
<repositories>
@ -27,41 +29,14 @@
<packaging>pom</packaging>
<properties>
<kotlin.version>1.3.41</kotlin.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
@ -121,7 +96,114 @@
<version>0.5.2</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.3.41</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.6.0</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>