();
34 |
35 | public static byte[] CPingType ={0x02};
36 |
37 |
38 | public byte[] BufDhPublicKey = Util.str_to_byte("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");
39 |
40 | public String DwRedirectIP ;
41 |
42 | public byte[] BufSigClientAddr ;
43 | public Date DwServerTime ;
44 | public byte[] DwServerTime_Byte ;
45 |
46 | public long TimeDifference ;
47 | public String DwClientIP ;
48 | public byte[] DwClientIP_Byte ;
49 |
50 | public short WClientPort ;
51 |
52 | public byte[] DwIsp = new byte[] {0x00,0x00,0x00,0x00};
53 | public byte[] DwIdc = new byte[] {0x00,0x00,0x12,0x00};
54 |
55 | public short WRedirectPort ;
56 | public byte[] WRedirectPort_Byte ;
57 |
58 | public byte[] BufDhShareKey = Util.str_to_byte("1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF");
59 |
60 | public String BufComputerName = "马化腾的灵魂战妓";
61 |
62 | public byte[] BufTgtgt ;
63 |
64 | public byte[] BRememberPwdLogin = {0x00};
65 | public byte[] BufComputerId_crc32_reversed = Util.str_to_byte("CCC2E96A");
66 | //public byte[] bufComputerID = Util.RandomKey();
67 | public byte[] BufComputerIdEx = Util.str_to_byte("7798000BAB5D4F3D3050652C4A2AF865");
68 |
69 | public byte[] BufComputerIdEx_crc32_reversed = Util.str_to_byte("3CDE845F");
70 |
71 | public byte[] BufDeviceId =Util.str_to_byte("0fabbe2104a72af1e19da1956a363df07b22ff2ec2cac92ba8d6da459d31a960");
72 |
73 | public byte[] BufComputerId ={ 0x43, 0x04, 0x21, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
74 |
75 |
76 | public byte[] BufTgtgtKey = Util.RandomKey();
77 |
78 | public byte[] BufSid = Util.str_to_byte("1EC12571B24CEA919A6E8DE6954ECE06");
79 | public byte[] BufMacGuid = Util.str_to_byte("214B1A0409ED1970987551BB2D3A7E0A");
80 | public byte[] BufSigPic;
81 |
82 | public byte[] QdData ;
83 | public byte[] BufQdKey =
84 | { 0x77, 0x45, 0x37, 0x5e, 0x33, 0x69, 0x6d, 0x67, 0x23, 0x69, 0x29, 0x25, 0x68, 0x31, 0x32, 0x5d };
85 | public byte[] DwQdVerion_Byte = {0x02,0x04,0x04,0x04};
86 | public byte[] QdSufFix = { 0x68 };
87 | public byte[] QdPreFix = { 0x3E };
88 | public short CQdProtocolVer = 0x0063;
89 | public byte[] CQdProtocolVer_Byte = {0x00,0x63};
90 | public long DwQdVerion = 0x02040404;
91 | public short WQdCsCmdNo = 0x0004;
92 | public byte[] WQdCsCmdNo_Byte = {0x00,0x04};
93 |
94 | public byte[] CQdCcSubNo = {0x00};
95 |
96 |
97 | public byte[] COsType = {0x03};
98 |
99 | public byte[] BIsWow64 = {0x01};
100 |
101 |
102 | public byte[] DwDrvVersionInfo = {0x01,0x02};
103 |
104 | public byte[] BufVersionTsSafeEditDat = Util.str_to_byte("07df000a000c0001");
105 |
106 | ///
107 | /// QScanEngine.dll的"文件版本"
108 | ///
109 | public byte[] BufVersionQScanEngineDll = { 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x20, 0x5c };
110 | public byte[] BufTgtGtKey ;
111 | public byte[] BufTgt ;
112 | public byte[] Buf16BytesGtKeySt ;
113 | public byte[] BufServiceTicket ;
114 | public byte[] Buf16BytesGtKeyStHttp ;
115 | public byte[] BufServiceTicketHttp ;
116 | public byte[] BufGtKeyTgtPwd ;
117 | public byte[] BufSessionKey ;
118 | public byte[] BufSigSession ;
119 | public byte[] BufPwdForConn ;
120 | public byte[] ClientKey ;
121 |
122 | public byte[] SessionKey ;
123 |
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/Utils/ByteBuilder.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.Utils;
2 | import net.newlydev.qqrobot.PCTIM.Utils.*;
3 |
4 | public class ByteBuilder
5 | {
6 | public byte[] data = new byte[]{};
7 |
8 | public ByteBuilder(){
9 |
10 | }
11 |
12 | public void clean()
13 | {
14 | this.data = new byte[]{};
15 | }
16 |
17 | public void writebytesbylength(byte[] to_write)
18 | {
19 | this.writeint(to_write.length);
20 | this.writebytes(to_write);
21 | }
22 |
23 |
24 | public void writebytes(byte[] to_write){
25 | this.data = Util.byteMerger(this.data,to_write);
26 | }
27 |
28 | public void rewritebytes(byte[] to_write){
29 | this.data = Util.byteMerger(to_write,this.data);
30 | }
31 |
32 | public void writebyte(byte to_write){
33 | this.data = Util.byteMerger(this.data,new byte[]{to_write});
34 | }
35 |
36 | public void writeint(int to_write){
37 | byte[] test = Util.subByte(Util.ToByte(to_write),2,2);
38 |
39 | this.data = Util.byteMerger(this.data,test);
40 | }
41 | public void rewriteint(int to_write)
42 | {
43 | byte[] test = Util.subByte(Util.ToByte(to_write),2,2);
44 |
45 | this.data = Util.byteMerger(test,this.data);
46 | }
47 |
48 |
49 | public void writelong(long to_write){
50 | byte[] test = Util.subByte(Util.ToByte(to_write),4,4);
51 |
52 | this.data = Util.byteMerger(this.data,test);
53 | }
54 | public void writeshort(short to_write){
55 | byte[] test = Util.subByte(Util.ToByte(to_write),0,4);
56 |
57 | this.data = Util.byteMerger(this.data,test);
58 | }
59 |
60 | public byte[] getdata(){
61 | return this.data;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/Utils/ByteFactory.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.Utils;
2 |
3 | import net.newlydev.qqrobot.PCTIM.Utils.*;
4 |
5 | public class ByteFactory
6 | {
7 | public int position = 0;
8 | public byte[] data = null;
9 | public ByteFactory(byte[] _data){
10 | this.data=_data;
11 | }
12 |
13 | public byte[] readrestBytes()
14 | {
15 | int length = this.data.length -this.position;
16 | byte[] test = Util.subByte(this.data,this.position,length);
17 | this.position +=length;
18 | return test;
19 | }
20 |
21 |
22 |
23 | public String readStringbylength(){
24 | int length = Util.GetInt(Util.subByte(this.data,this.position,2));
25 | this.position +=2;
26 | byte[] test = Util.subByte(this.data,position,length);
27 | this.position +=length;
28 | return new String(test);
29 | }
30 |
31 | public String readString(int length)
32 | {
33 | byte[] test = Util.subByte(this.data,position,length);
34 | this.position +=length;
35 | return new String(test);
36 | }
37 |
38 |
39 | public byte[] readBytesbylength(){
40 | int length = Util.GetInt(Util.subByte(this.data,this.position,2));
41 | this.position +=2;
42 | byte[] test = Util.subByte(this.data,position,length);
43 | this.position +=length;
44 | return test;
45 | }
46 |
47 | public byte[] readBytes(int length){
48 | byte[] test = Util.subByte(this.data,this.position,length);
49 | this.position +=length;
50 | return test;
51 | }
52 |
53 |
54 |
55 | public int readint(){
56 | int test = Util.GetInt(Util.subByte(this.data,this.position,2));
57 | this.position +=2;
58 | return test;
59 | }
60 |
61 |
62 |
63 | public long readlong()
64 | {
65 | long test = Util.GetLong(Util.subByte(this.data,this.position,4));
66 | this.position +=4;
67 | return test;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/Utils/Crypter.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.Utils;
2 | import java.io.*;
3 | import java.util.*;
4 |
5 | /**
6 | * 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
7 | * 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
8 | * f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
9 | * prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
10 | * preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
11 | * 填充的字节数与原始明文长度有关,填充的方法是:
12 | *
13 | *
14 | * *
15 | * ------- 消息填充算法 -----------
16 | * a = (明文长度 + 10) mod 8
17 | * if(a 不等于 0) a = 8 - a;
18 | * b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来
19 | * plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
20 | * plain[1 至 a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
21 | * plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
22 | * plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
23 | * ------- 消息填充算法 ------------ *
24 | *
25 | *
26 | */
27 | public class Crypter {
28 | // 指向当前的明文块
29 | private byte[] plain;
30 | // 这指向前面一个明文块
31 | private byte[] prePlain;
32 | // 输出的密文或者明文
33 | private byte[] out;
34 | // 当前加密的密文位置和上一次加密的密文块位置,他们相差8
35 | private int crypt, preCrypt;
36 | // 当前处理的加密解密块的位置
37 | private int pos;
38 | // 填充数
39 | private int padding;
40 | // 密钥
41 | private byte[] key;
42 | // 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
43 | // 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
44 | private boolean header = true;
45 | // 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
46 | // 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
47 | private int contextStart;
48 | // 随机数对象
49 | private static Random random = CrypterUtil.random();
50 | // 字节输出流
51 | private ByteArrayOutputStream baos;
52 |
53 | /**
54 | * 构造函数
55 | */
56 | public Crypter() {
57 | baos = new ByteArrayOutputStream(8);
58 | }
59 |
60 | /**
61 | * 解密
62 | * @param in 密文
63 | * @param offset 密文开始的位置
64 | * @param len 密文长度
65 | * @param k 密钥
66 | * @return 明文
67 | */
68 | public byte[] decrypt(byte[] in, int offset, int len, byte[] k) {
69 | // 检查密钥
70 | if(k == null)
71 | return null;
72 |
73 | crypt = preCrypt = 0;
74 | this.key = k;
75 | int count;
76 | byte[] m = new byte[offset + 8];
77 |
78 | // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
79 | if((len % 8 != 0) || (len < 16)) return null;
80 | // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
81 | prePlain = decipher(in, offset);
82 | pos = prePlain[0] & 0x7;
83 | // 得到真正明文的长度
84 | count = len - pos - 10;
85 | // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
86 | if(count < 0) return null;
87 |
88 | // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
89 | // 第一个8字节块也没有preCrypt,所有这里建一个全0的
90 | for(int i = offset; i < m.length; i++)
91 | m[i] = 0;
92 | // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
93 | out = new byte[count];
94 | // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
95 | preCrypt = 0;
96 | // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
97 | crypt = 8;
98 | // 自然这个也是8
99 | contextStart = 8;
100 | // 加1,和加密算法是对应的
101 | pos++;
102 |
103 | // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
104 | // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
105 | // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
106 | // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
107 | padding = 1;
108 | while(padding <= 2) {
109 | if(pos < 8) {
110 | pos++;
111 | padding++;
112 | }
113 | if(pos == 8) {
114 | m = in;
115 | if(!decrypt8Bytes(in, offset, len)) return null;
116 | }
117 | }
118 |
119 | // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
120 | // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
121 | int i = 0;
122 | while(count != 0) {
123 | if(pos < 8) {
124 | out[i] = (byte)(m[offset + preCrypt + pos] ^ prePlain[pos]);
125 | i++;
126 | count--;
127 | pos++;
128 | }
129 | if(pos == 8) {
130 | m = in;
131 | preCrypt = crypt - 8;
132 | if(!decrypt8Bytes(in, offset, len))
133 | return null;
134 | }
135 | }
136 |
137 | // 最后的解密部分,上面一个while已经把明文都解出来了,就剩下尾部的填充了,应该全是0
138 | // 所以这里有检查是否解密了之后是不是0,如果不是的话那肯定出错了,返回null
139 | for(padding = 1; padding < 8; padding++) {
140 | if(pos < 8) {
141 | if((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0)
142 | return null;
143 | pos++;
144 | }
145 | if(pos == 8) {
146 | m = in;
147 | preCrypt = crypt;
148 | if(!decrypt8Bytes(in, offset, len))
149 | return null;
150 | }
151 | }
152 | return out;
153 | }
154 |
155 | /**
156 | * @param in
157 | * 需要被解密的密文
158 | * @param inLen
159 | * 密文长度
160 | * @param k
161 | * 密钥
162 | * @return Message 已解密的消息
163 | */
164 | public byte[] decrypt(byte[] in, byte[] k) {
165 | return decrypt(in, 0, in.length, k);
166 | }
167 |
168 | /**
169 | * 加密
170 | * @param in 明文字节数组
171 | * @param offset 开始加密的偏移
172 | * @param len 加密长度
173 | * @param k 密钥
174 | * @return 密文字节数组
175 | */
176 | public byte[] encrypt(byte[] in, int offset, int len, byte[] k) {
177 | // 检查密钥
178 | if(k == null)
179 | return in;
180 |
181 | plain = new byte[8];
182 | prePlain = new byte[8];
183 | pos = 1;
184 | padding = 0;
185 | crypt = preCrypt = 0;
186 | this.key = k;
187 | header = true;
188 |
189 | // 计算头部填充字节数
190 | pos = (len + 0x0A) % 8;
191 | if(pos != 0)
192 | pos = 8 - pos;
193 | // 计算输出的密文长度
194 | out = new byte[len + pos + 10];
195 | // 这里的操作把pos存到了plain的第一个字节里面
196 | // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
197 | plain[0] = (byte)((rand() & 0xF8) | pos);
198 |
199 | // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
200 | for(int i = 1; i <= pos; i++)
201 | plain[i] = (byte)(rand() & 0xFF);
202 | pos++;
203 | // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
204 | for(int i = 0; i < 8; i++)
205 | prePlain[i] = 0x0;
206 |
207 | // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
208 | padding = 1;
209 | while(padding <= 2) {
210 | if(pos < 8) {
211 | plain[pos++] = (byte)(rand() & 0xFF);
212 | padding++;
213 | }
214 | if(pos == 8)
215 | encrypt8Bytes();
216 | }
217 |
218 | // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
219 | int i = offset;
220 | while(len > 0) {
221 | if(pos < 8) {
222 | plain[pos++] = in[i++];
223 | len--;
224 | }
225 | if(pos == 8)
226 | encrypt8Bytes();
227 | }
228 |
229 | // 最后填上0,以保证是8字节的倍数
230 | padding = 1;
231 | while(padding <= 7) {
232 | if(pos < 8) {
233 | plain[pos++] = 0x0;
234 | padding++;
235 | }
236 | if(pos == 8)
237 | encrypt8Bytes();
238 | }
239 |
240 | return out;
241 | }
242 |
243 | /**
244 | * @param in
245 | * 需要加密的明文
246 | * @param inLen
247 | * 明文长度
248 | * @param k
249 | * 密钥
250 | * @return Message 密文
251 | */
252 | public byte[] encrypt(byte[] in, byte[] k) {
253 | return encrypt(in, 0, in.length, k);
254 | }
255 |
256 | /**
257 | * 加密一个8字节块
258 | *
259 | * @param in
260 | * 明文字节数组
261 | * @return
262 | * 密文字节数组
263 | */
264 | private byte[] encipher(byte[] in) {
265 | // 迭代次数,16次
266 | int loop = 0x10;
267 | // 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
268 | // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
269 | // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
270 | long y = CrypterUtil.getUnsignedInt(in, 0, 4);
271 | long z = CrypterUtil.getUnsignedInt(in, 4, 4);
272 | long a = CrypterUtil.getUnsignedInt(key, 0, 4);
273 | long b = CrypterUtil.getUnsignedInt(key, 4, 4);
274 | long c = CrypterUtil.getUnsignedInt(key, 8, 4);
275 | long d = CrypterUtil.getUnsignedInt(key, 12, 4);
276 | // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
277 | // 这个数是TEA算法的delta,实际是就是(sqr(5) - 1) * 2^31 (根号5,减1,再乘2的31次方)
278 | long sum = 0;
279 | long delta = 0x9E3779B9;
280 | delta &= 0xFFFFFFFFL;
281 |
282 | // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
283 | while (loop-- > 0) {
284 | sum += delta;
285 | sum &= 0xFFFFFFFFL;
286 | y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
287 | y &= 0xFFFFFFFFL;
288 | z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
289 | z &= 0xFFFFFFFFL;
290 | }
291 |
292 | // 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
293 | baos.reset();
294 | writeInt((int)y);
295 | writeInt((int)z);
296 | return baos.toByteArray();
297 | }
298 |
299 | /**
300 | * 解密从offset开始的8字节密文
301 | *
302 | * @param in
303 | * 密文字节数组
304 | * @param offset
305 | * 密文开始位置
306 | * @return
307 | * 明文
308 | */
309 | private byte[] decipher(byte[] in, int offset) {
310 | // 迭代次数,16次
311 | int loop = 0x10;
312 | // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
313 | // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
314 | // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
315 | long y = CrypterUtil.getUnsignedInt(in, offset, 4);
316 | long z = CrypterUtil.getUnsignedInt(in, offset + 4, 4);
317 | long a = CrypterUtil.getUnsignedInt(key, 0, 4);
318 | long b = CrypterUtil.getUnsignedInt(key, 4, 4);
319 | long c = CrypterUtil.getUnsignedInt(key, 8, 4);
320 | long d = CrypterUtil.getUnsignedInt(key, 12, 4);
321 | // 算法的一些控制变量,sum在这里也有数了,这个sum和迭代次数有关系
322 | // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
323 | // 得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样才能解密呀~~
324 | long sum = 0xE3779B90;
325 | sum &= 0xFFFFFFFFL;
326 | long delta = 0x9E3779B9;
327 | delta &= 0xFFFFFFFFL;
328 |
329 | // 迭代开始了, @_@
330 | while(loop-- > 0) {
331 | z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
332 | z &= 0xFFFFFFFFL;
333 | y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
334 | y &= 0xFFFFFFFFL;
335 | sum -= delta;
336 | sum &= 0xFFFFFFFFL;
337 | }
338 |
339 | baos.reset();
340 | writeInt((int)y);
341 | writeInt((int)z);
342 | return baos.toByteArray();
343 | }
344 |
345 | /**
346 | * 写入一个整型到输出流,高字节优先
347 | *
348 | * @param t
349 | */
350 | private void writeInt(int t) {
351 | baos.write(t >>> 24);
352 | baos.write(t >>> 16);
353 | baos.write(t >>> 8);
354 | baos.write(t);
355 | }
356 |
357 | /**
358 | * 解密
359 | *
360 | * @param in
361 | * 密文
362 | * @return
363 | * 明文
364 | */
365 | private byte[] decipher(byte[] in) {
366 | return decipher(in, 0);
367 | }
368 |
369 | /**
370 | * 加密8字节
371 | */
372 | private void encrypt8Bytes() {
373 | // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
374 | for(pos = 0; pos < 8; pos++) {
375 | if(header)
376 | plain[pos] ^= prePlain[pos];
377 | else
378 | plain[pos] ^= out[preCrypt + pos];
379 | }
380 | // 这个完成我上面说的 f(plain ^ preCrypt)
381 | byte[] crypted = encipher(plain);
382 | // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
383 | System.arraycopy(crypted, 0, out, crypt, 8);
384 |
385 | // 这个完成了 f(plain ^ preCrypt) ^ prePlain,ok,下面拷贝一下就行了
386 | for(pos = 0; pos < 8; pos++)
387 | out[crypt + pos] ^= prePlain[pos];
388 | System.arraycopy(plain, 0, prePlain, 0, 8);
389 |
390 | // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
391 | preCrypt = crypt;
392 | crypt += 8;
393 | pos = 0;
394 | header = false;
395 | }
396 |
397 | /**
398 | * 解密8个字节
399 | *
400 | * @param in
401 | * 密文字节数组
402 | * @param offset
403 | * 从何处开始解密
404 | * @param len
405 | * 密文的长度
406 | * @return
407 | * true表示解密成功
408 | */
409 | private boolean decrypt8Bytes(byte[] in , int offset, int len) {
410 | // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
411 | for(pos = 0; pos < 8; pos++) {
412 | if(contextStart + pos >= len)
413 | return true;
414 | prePlain[pos] ^= in[offset + crypt + pos];
415 | }
416 |
417 | // 好,这里执行到了 d(crypt ^ prePlain)
418 | prePlain = decipher(prePlain);
419 | if(prePlain == null)
420 | return false;
421 |
422 | // 解密完成,最后一步好像没做?
423 | // 这里最后一步放到decrypt里面去做了,因为解密的步骤有点不太一样
424 | // 调整这些变量的值先
425 | contextStart += 8;
426 | crypt += 8;
427 | pos = 0;
428 | return true;
429 | }
430 |
431 | /**
432 | * 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值
433 | * 随机因子可以使相同的明文每次加密出来的密文都不一样
434 | *
435 | * @return
436 | * 随机因子
437 | */
438 | private int rand() {
439 | return random.nextInt();
440 | }
441 |
442 | }
443 |
444 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/Utils/Util.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.Utils;
2 | import android.graphics.*;
3 | import java.io.*;
4 | import java.net.*;
5 | import java.nio.*;
6 | import java.security.*;
7 | import java.security.cert.*;
8 | import java.text.*;
9 | import java.util.*;
10 | import java.util.zip.*;
11 | import javax.net.ssl.*;
12 | import net.newlydev.qqrobot.PCTIM.*;
13 | import net.newlydev.qqrobot.PCTIM.Message.*;
14 | import net.newlydev.qqrobot.PCTIM.sdk.*;
15 |
16 | public class Util
17 | {
18 | private static Date BaseDateTime = new Date(0);
19 | public static String ua = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36";
20 |
21 | public static HostnameVerifier hv = new HostnameVerifier() {
22 | public boolean verify(String urlHostName, SSLSession session)
23 | {
24 | return true;
25 | }
26 | };
27 |
28 | public static void getquncookie(QQUser user)
29 | {
30 | try
31 | {
32 | String urls="https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin=" + user.QQ + "&clientkey=" + Util.byte2HexString(user.TXProtocol.BufServiceTicketHttp).replaceAll(" ", "") + "&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441";
33 | URL lll = new URL(urls);
34 | HttpURLConnection connection = (HttpURLConnection) lll.openConnection();// 打开连接
35 | connection.setRequestMethod("GET");
36 | connection.setRequestProperty("User-Agent", ua);
37 | connection.getResponseCode();
38 | List cookies = connection.getHeaderFields().get("Set-Cookie");
39 | user.userskey = "";
40 | for (String cookie : cookies)
41 | {
42 | if (cookie.matches("skey=.*"))
43 | {
44 | user.userskey = cookie.replaceAll("skey=", "").replaceAll(";.*", "");
45 | user.bkn = Util.GetBkn(user.userskey);
46 | }
47 | if (cookie.matches("p_skey=.*"))
48 | {
49 | user.pskey = cookie.replaceAll("p_skey=", "").replaceAll(";.*", "");
50 | user.qungtk = Util.GET_GTK(user.pskey);
51 | }
52 | user.quncookie += cookie.replaceAll("Path=.*$", "").replaceAll("Expires=.*$", "") + " " ;
53 | }
54 | String url = connection.getHeaderField("Location");
55 | fuck(url, user);
56 | connection.disconnect();
57 | }
58 | catch (Exception e)
59 | {
60 | e.printStackTrace();
61 | }
62 | }
63 |
64 | public static void fuck(String url, QQUser user)
65 | {
66 | try
67 | {
68 | URL lll = new URL(url);
69 | HttpURLConnection connection = (HttpURLConnection) lll.openConnection();// 打开连接
70 | connection.setRequestMethod("GET");
71 | connection.setRequestProperty("User-Agent", ua);
72 | connection.setInstanceFollowRedirects(false);
73 | connection.getResponseCode();
74 | List cookies = connection.getHeaderFields().get("Set-Cookie");
75 |
76 | for (String cookie : cookies)
77 | {
78 | if (cookie.matches("skey=.*"))
79 | {
80 | user.userskey = cookie.replaceAll("skey=", "").replaceAll(";.*", "");
81 | user.bkn = Util.GetBkn(user.userskey);
82 | }
83 | if (cookie.matches("p_skey=.*"))
84 | {
85 | user.pskey = cookie.replaceAll("p_skey=", "").replaceAll(";.*", "");
86 | user.qungtk = Util.GET_GTK(user.pskey);
87 | }
88 | user.quncookie += cookie.replaceAll("Path=.*$", "").replaceAll("Expires=.*$", "") + " " ;
89 | }
90 | connection.disconnect();
91 | }
92 | catch (Exception e)
93 | {
94 | e.printStackTrace();
95 | }
96 | }
97 |
98 | public static void trustAllHttpsCertificates() throws Exception
99 | {
100 | TrustManager[] trustAllCerts = new TrustManager[1];
101 | TrustManager tm = new miTM();
102 | trustAllCerts[0] = tm;
103 | SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");
104 | sc.init(null, trustAllCerts, null);
105 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
106 | }
107 | public static String getMD5(byte[] bytes)
108 | {
109 | ByteArrayInputStream fileInputStream=new ByteArrayInputStream(bytes);
110 | try
111 | {
112 | MessageDigest MD5 = MessageDigest.getInstance("MD5");
113 | byte[] buffer = new byte[8192];
114 | int length;
115 | while ((length = fileInputStream.read(buffer)) != -1)
116 | {
117 | MD5.update(buffer, 0, length);
118 | }
119 | return byte2HexString(MD5.digest()).replace(" ", "");
120 | }
121 | catch (Exception e)
122 | {
123 | e.printStackTrace();
124 | return null;
125 | }
126 | finally
127 | {
128 | try
129 | {
130 | if (fileInputStream != null)
131 | {
132 | fileInputStream.close();
133 | }
134 | }
135 | catch (IOException e)
136 | {
137 | e.printStackTrace();
138 | }
139 | }
140 | }
141 |
142 | public static void log(String string)
143 | {
144 | SimpleDateFormat format0 = new SimpleDateFormat("[HH:mm:ss]");
145 | String log = format0.format(new Date().getTime());
146 | try
147 | {
148 | FileWriter fw=new FileWriter("/sdcard/qqrobot.log", true);
149 | fw.write(log + " " + string + "\n");
150 | fw.close();
151 | }
152 | catch (IOException e)
153 | {}
154 | }
155 |
156 | public static byte[] reverse_byte(byte[] data)
157 | {
158 | byte[] Fuck = new byte[data.length];
159 | for (int time = 0;time < data.length;time++)
160 | {
161 | Fuck[time] = data[data.length - time - 1];
162 |
163 | }
164 |
165 | return Fuck;
166 | }
167 |
168 | public static long getfilelength(String file)
169 | {
170 | File f= new File(file);
171 | return f.length();
172 | }
173 |
174 | public static byte[] get_crc32(byte[] data)
175 | {
176 | CRC32 crc32 = new CRC32();
177 | crc32.update(data);
178 | return reverse_byte(str_to_byte(Long.toHexString(crc32.getValue())));
179 | }
180 | public static String getMD5(File file)
181 | {
182 | FileInputStream fileInputStream = null;
183 | try
184 | {
185 | MessageDigest MD5 = MessageDigest.getInstance("MD5");
186 | fileInputStream = new FileInputStream(file);
187 | byte[] buffer = new byte[8192];
188 | int length;
189 | while ((length = fileInputStream.read(buffer)) != -1)
190 | {
191 | MD5.update(buffer, 0, length);
192 | }
193 | return byte2HexString(MD5.digest()).replace(" ", "");
194 | }
195 | catch (Exception e)
196 | {
197 | e.printStackTrace();
198 | return null;
199 | }
200 | finally
201 | {
202 | try
203 | {
204 | if (fileInputStream != null)
205 | {
206 | fileInputStream.close();
207 | }
208 | }
209 | catch (IOException e)
210 | {
211 | e.printStackTrace();
212 | }
213 | }
214 | }
215 | public static String getMD5(Bitmap bitmap)
216 | {
217 | return getMD5(Bufferedimg_tobytes(bitmap));
218 | }
219 | public static String GetMD5HashFromFile(String fileName)
220 | {
221 |
222 | File file = new File(fileName);
223 |
224 | return getMD5(file);
225 | }
226 |
227 |
228 | public static String GetMD5ToGuidHashFromFile(String fileName)
229 | {
230 | String md5 = GetMD5HashFromFile(fileName);
231 | return md5.substring(0, 8) + "-" + md5.substring(8, 12) + "-" + md5.substring(12, 16) + "-" + md5.substring(16, 20) + "-" + md5.substring(20, md5.length());
232 | }
233 |
234 | public static String GetMD5ToGuidHashFromBitmap(Bitmap bitmap) throws IOException
235 | {
236 | String md5 = getMD5(bitmap);
237 | return md5.substring(0, 8) + "-" + md5.substring(8, 12) + "-" + md5.substring(12, 16) + "-" + md5.substring(16, 20) + "-" + md5.substring(20, md5.length());
238 | }
239 | public static String GetMD5ToGuidHashFromBytes(byte[] bytes) throws IOException
240 | {
241 | String md5 = getMD5(bytes);
242 | return md5.substring(0, 8) + "-" + md5.substring(8, 12) + "-" + md5.substring(12, 16) + "-" + md5.substring(16, 20) + "-" + md5.substring(20, md5.length());
243 | }
244 | public static String http_dns(String host)
245 | {
246 | try
247 | {
248 | URL lll = new URL("http://119.29.29.29/d?dn=" + host);
249 | HttpURLConnection connection = (HttpURLConnection) lll.openConnection();// 打开连接
250 | connection.setRequestMethod("GET");
251 | connection.getResponseCode();
252 | BufferedReader br= new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
253 | String line;
254 | StringBuilder sb = new StringBuilder();
255 | while ((line = br.readLine()) != null)
256 | {
257 | sb.append(line);
258 | }
259 | br.close();
260 | connection.disconnect();
261 | String hosts = sb.toString();
262 | if (hosts.contains(";"))
263 | {
264 | return hosts.split(";")[0];
265 | }
266 | else
267 | {
268 | return hosts;
269 | }
270 | }
271 | catch (Exception e)
272 | {
273 | System.out.println(e.toString());
274 | }
275 | return null;
276 | }
277 |
278 | public static PictureStore uploadimg(PictureKeyStore keystore, QQUser user, int pictureid)
279 | {
280 | PictureStore store = null;
281 | for (PictureStore onestore: user.imgs)
282 | {
283 | if (onestore.pictureid == pictureid)
284 | {
285 | store = onestore;
286 | user.imgs.remove(onestore);
287 | break;
288 | }
289 | }
290 | URL u = null;
291 | HttpURLConnection con = null;
292 | try
293 | {
294 | u = new URL("http://" + Util.http_dns("htdata2.qq.com") + "/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey=" + Util.byte2HexString(keystore.ukey).replace(" ", "") + "&filesize=" + store.data.length + "&range=0&uin=" + user.QQ + "&groupcode=" + store.Group);
295 | con = (HttpURLConnection) u.openConnection();
296 | con.setRequestMethod("POST");
297 | con.setDoOutput(true);
298 | con.setDoInput(true);
299 | con.setUseCaches(false);
300 | con.setRequestProperty("Content-Type", "binary/octet-stream");
301 | con.setRequestProperty("User-Agent", "QQClient");
302 | OutputStream outStream = con.getOutputStream();
303 | DataInputStream in = new DataInputStream(new ByteArrayInputStream(store.data));
304 | byte[] bufferOut = new byte[1024];
305 | int bytes = 0;
306 | while ((bytes = in.read(bufferOut)) != -1)
307 | {
308 | outStream.write(bufferOut, 0, bytes);
309 | }
310 | in.close();
311 | outStream.flush();
312 | outStream.close();
313 | con.getResponseCode();
314 | DataInputStream dis=new DataInputStream(con.getInputStream());
315 | dis.readLine();
316 | }
317 | catch (Exception e)
318 | {
319 | e.printStackTrace();
320 | }
321 | return store;
322 |
323 | }
324 |
325 | public static String Length_toPB(String d)
326 | {
327 | String binary = Long.toString(GetQQNumRetUint(d), 2);
328 | String temp = "";
329 | while (!(binary).isEmpty())
330 | {
331 | temp = temp + binary.substring(binary.length() - 7, binary.length());
332 | if (binary.length() >= 8)
333 | {
334 | binary = binary.substring(0, binary.length() - 8);
335 | }
336 | else
337 | {
338 | break;
339 | }
340 | }
341 |
342 | return Long.toHexString(Long.parseLong(temp, 2));
343 | }
344 |
345 | public static long GetQQNumRetUint(String six)
346 | {
347 | return Long.parseLong(six.replace(" ", ""), 16);
348 | }
349 |
350 | public static String PB_toLength(long d)
351 | {
352 | String binary = Long.toString(d, 2);
353 | String temp = "";
354 | while (!(binary.isEmpty() || binary == null))
355 | {
356 | String binary1 = "0000000" + binary;
357 | temp = temp + "1" + binary1.substring(binary1.length() - 7, binary1.length());
358 | if (binary.length() >= 7)
359 | {
360 | binary = binary.substring(0, (binary.length() - 7));
361 | }
362 | else
363 | {
364 | break;
365 | }
366 | }
367 | String temp1 = temp.substring(temp.length() - 7, temp.length());
368 | temp = temp.substring(0, temp.length() - 8) + "0" + temp1;
369 | return Long.toHexString(Long.parseLong(temp, 2));
370 | }
371 |
372 | public static byte[] Bufferedimg_tobytes(Bitmap img)
373 | {
374 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
375 | img.compress(Bitmap.CompressFormat.JPEG, 100, baos);
376 | return baos.toByteArray();
377 | }
378 |
379 | public static byte[] constructxmlmessage(QQUser user, byte[] data)
380 | {
381 |
382 | ByteBuilder builder = new ByteBuilder();
383 | builder.writebytes(Util.RandomKey(4));
384 | builder.writebytes(Util.str_to_byte("0000000009008600"));
385 | builder.writebytes(new byte[]{0x00,0x0c});
386 | builder.writebytes(Util.str_to_byte("E5BEAEE8BDAFE99B85E9BB91"));
387 | builder.writebytes(new byte[] { 0x00, 0x00, 0x14 });
388 | builder.writeint(data.length + 11);
389 | builder.writebyte((byte) 0x01);
390 | builder.writeint(data.length + 1);
391 | builder.writebyte((byte) 0x01);
392 | builder.writebytes(data);
393 | builder.writebytes(new byte[] { 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4D });
394 | return builder.getdata();
395 | }
396 |
397 | public static byte[] constructmessage(QQUser user, byte[] data)
398 | {
399 | ByteBuilder builder = new ByteBuilder();
400 | builder.writebyte((byte)0x01);
401 | builder.writeint((data.length + 3));
402 | builder.writebyte((byte)0x01);
403 | builder.writeint(data.length);
404 | builder.writebytes(data);
405 |
406 | return builder.getdata();
407 | }
408 | public static long ConvertQQGroupId(long code)
409 | {
410 | String group = String.valueOf(code);
411 | long left = Long.parseLong(group.substring(0, group.length() - 6));
412 |
413 | String right = "", gid = "";
414 | if (left >= 1 && left <= 10)
415 | {
416 | right = group.substring(group.length() - 6, group.length());
417 | gid = String.valueOf(left + 202) + right;
418 | }
419 | else if (left >= 11 && left <= 19)
420 | {
421 | right = group.substring(group.length() - 6, group.length());
422 | gid = String.valueOf(left + 469) + right;
423 | }
424 | else if (left >= 20 && left <= 66)
425 | {
426 | left = Long.parseLong(String.valueOf(left).substring(0, 1));
427 | right = group.substring(group.length() - 7, group.length());
428 | gid = String.valueOf(left + 208) + right;
429 | }
430 | else if (left >= 67 && left <= 156)
431 | {
432 | right = group.substring(group.length() - 6, group.length());
433 | gid = String.valueOf(left + 1943) + right;
434 | }
435 | else if (left >= 157 && left <= 209)
436 | {
437 | left = Long.parseLong(String.valueOf(left).substring(0, 2));
438 | right = group.substring(group.length() - 7, group.length());
439 | gid = String.valueOf(left + 199) + right;
440 | }
441 | else if (left >= 210 && left <= 309)
442 | {
443 | left = Long.parseLong(String.valueOf(left).substring(0, 2));
444 | right = group.substring(group.length() - 7, group.length());
445 | gid = String.valueOf(left + 389) + right;
446 | }
447 | else if (left >= 310 && left <= 499)
448 | {
449 | left = Long.parseLong(String.valueOf(left).substring(0, 2));
450 | right = group.substring(group.length() - 7, group.length());
451 | gid = String.valueOf(left + 349) + right;
452 | }
453 | else
454 | {
455 | return code;
456 | }
457 | return Long.parseLong(gid);
458 | }
459 |
460 |
461 | public static void parseRichText(QQMessage qqmessage, byte[] rich_data)
462 | {
463 | ByteFactory bytefactory = new ByteFactory(rich_data);
464 | int messagetype = bytefactory.readBytes(1)[0];
465 | int messagelength = bytefactory.readint();
466 | int position = bytefactory.position;
467 | while (position + messagelength <= bytefactory.data.length)
468 | {
469 | bytefactory.readBytes(1);
470 | switch (messagetype)
471 | {
472 | case 0x01: // 纯文本消息、@
473 | {
474 | qqmessage.contain_type = 1;
475 | String messageStr = bytefactory.readStringbylength();
476 | if (messageStr.startsWith("@") && position + messagelength - bytefactory.position == 16)
477 | {
478 | if (qqmessage.Isat == false)
479 | {
480 | qqmessage.Isat = true;
481 | }
482 | bytefactory.readBytes(10);
483 | qqmessage.Atlist.add(messageStr + " Target: " + bytefactory.readlong());
484 | }
485 | else
486 | {
487 | qqmessage.Message += messageStr;
488 | }
489 | break;
490 | }
491 | case 0x03: // 图片
492 | {
493 | qqmessage.contain_type = 2;
494 | qqmessage.Message += bytefactory.readStringbylength();
495 | //log(Util.byte2HexString(rich_data));
496 | //log(new String(rich_data));
497 | break;
498 | }
499 | case 0x0A: // 音频
500 | {
501 | qqmessage.contain_type = 3;
502 | qqmessage.Message += bytefactory.readBytesbylength();
503 | break;
504 | }
505 | case 0x0E: // 未知
506 | {
507 | break;
508 | }
509 | case 0x12: // 群名片
510 | {
511 | ByteFactory cardfactory = new ByteFactory(Util.subByte(rich_data, position, rich_data.length - position));
512 | int cardtype = cardfactory.readBytes(1)[0];
513 |
514 | int cardlength = cardfactory.readint();
515 | int cardposition = cardfactory.position;
516 |
517 | while (cardposition + cardlength <= cardfactory.data.length)
518 | {
519 |
520 |
521 | if (cardtype == 0x01 || cardtype == 0x02)
522 | {
523 | qqmessage.SendName = cardfactory.readString(cardlength);
524 | }
525 |
526 | if (cardposition + cardlength == cardfactory.data.length)
527 | {
528 | break;
529 | }
530 | cardfactory.readBytes((cardposition + cardlength - cardfactory.position));
531 |
532 | cardtype = cardfactory.readBytes(1)[0];
533 | cardlength = cardfactory.readint();
534 | cardposition = cardfactory.position;
535 | }
536 |
537 |
538 | break;
539 | }
540 | case 0x14: // xml
541 | {
542 | qqmessage.contain_type = 1;
543 | ByteFactory xmlfactory = new ByteFactory(Util.subByte(rich_data, position, rich_data.length - position));
544 | xmlfactory.readBytes(1);
545 | int length = xmlfactory.readint();
546 | xmlfactory.readBytes(1);
547 | byte[] xml = xmlfactory.readBytes(length);
548 | qqmessage.Message = new String(ZLibUtils.decompress(xml));
549 |
550 | break;
551 |
552 | }
553 | case 0x18: // 群文件
554 | {
555 | System.out.println("Fuck");
556 | break;
557 | }
558 | case 0x19: // 红包
559 | {
560 | break;
561 |
562 | }
563 | default:
564 | {
565 | break;
566 | }
567 | }
568 | if (position + messagelength == bytefactory.data.length)
569 | {
570 | break;
571 | }
572 | bytefactory.readBytes((position + messagelength - bytefactory.position));
573 | messagetype = bytefactory.readBytes(1)[0];
574 | messagelength = bytefactory.readint();
575 | position = bytefactory.position;
576 | }
577 | }
578 |
579 |
580 |
581 | public static String getHostIP()
582 | {
583 |
584 | String hostIp = null;
585 | try
586 | {
587 | Enumeration nis = NetworkInterface.getNetworkInterfaces();
588 | InetAddress ia = null;
589 | while (nis.hasMoreElements())
590 | {
591 | NetworkInterface ni = (NetworkInterface) nis.nextElement();
592 | Enumeration ias = ni.getInetAddresses();
593 | while (ias.hasMoreElements())
594 | {
595 | ia = ias.nextElement();
596 | if (ia instanceof Inet6Address)
597 | {
598 | continue;// skip ipv6
599 | }
600 | String ip = ia.getHostAddress();
601 | if (!"127.0.0.1".equals(ip))
602 | {
603 | hostIp = ia.getHostAddress();
604 | break;
605 | }
606 | }
607 | }
608 | }
609 | catch (SocketException e)
610 | {
611 |
612 | e.printStackTrace();
613 | }
614 | return hostIp;
615 | }
616 | public static long GetTimeSeconds(Date dateTime)
617 | {
618 | return (long) dateTime.getTime() - BaseDateTime.getTime() / 1000;
619 | }
620 |
621 |
622 | public static byte[] GetQdData(QQUser user)
623 | {
624 | byte[] data = new byte[]{};
625 | data = Util.byteMerger(data, Util.IPStringToByteArray(user.TXProtocol.DwServerIP));
626 | byte[] qddata = new byte[]{};
627 | qddata = Util.byteMerger(qddata, user.TXProtocol.DwQdVerion_Byte);
628 | qddata = Util.byteMerger(qddata, new byte[]{0x00,0x00,0x00,0x00});
629 | qddata = Util.byteMerger(qddata, user.TXProtocol.DwPubNo);
630 | qddata = Util.byteMerger(qddata, Util.subByte(Util.ToByte(user.QQ), 4, 4));
631 | qddata = Util.byteMerger(qddata, Util.subByte(Util.ToByte(data.length), 2, 2));
632 |
633 | data = new byte[]{};
634 | data = Util.byteMerger(data, user.TXProtocol.QdPreFix);
635 | data = Util.byteMerger(data, user.TXProtocol.CQdProtocolVer_Byte);
636 | data = Util.byteMerger(data, user.TXProtocol.DwQdVerion_Byte);
637 | data = Util.byteMerger(data, new byte[]{0x00});
638 | data = Util.byteMerger(data, user.TXProtocol.WQdCsCmdNo_Byte);
639 | data = Util.byteMerger(data, user.TXProtocol.CQdCcSubNo);
640 | data = Util.byteMerger(data, Util.str_to_byte("0E88"));
641 | data = Util.byteMerger(data, new byte[]{0x00,0x00,0x00,0x00});
642 | data = Util.byteMerger(data, user.TXProtocol.BufComputerIdEx);
643 | data = Util.byteMerger(data, user.TXProtocol.COsType);
644 | data = Util.byteMerger(data, user.TXProtocol.BIsWow64);
645 | data = Util.byteMerger(data, user.TXProtocol.DwPubNo);
646 | data = Util.byteMerger(data, Util.subByte(user.TXProtocol.DwClientVer, 2, 2));
647 | data = Util.byteMerger(data, new byte[]{0x00,0x00});
648 | data = Util.byteMerger(data, user.TXProtocol.DwDrvVersionInfo);
649 | data = Util.byteMerger(data, new byte[]{0x00,0x00,0x00,0x00});
650 | data = Util.byteMerger(data, user.TXProtocol.BufVersionTsSafeEditDat);
651 | data = Util.byteMerger(data, user.TXProtocol.BufVersionQScanEngineDll);
652 | data = Util.byteMerger(data, new byte[]{0x00});
653 | Crypter crypter = new Crypter();
654 | data = Util.byteMerger(data, crypter.encrypt(qddata, user.TXProtocol.BufQdKey));
655 | data = Util.byteMerger(data, user.TXProtocol.QdSufFix);
656 |
657 | int size = data.length + 3;
658 | qddata = new byte[]{};
659 | qddata = Util.byteMerger(qddata, user.TXProtocol.QdPreFix);
660 | qddata = Util.byteMerger(qddata, Util.subByte(Util.ToByte(size), 2, 6));
661 | qddata = Util.byteMerger(qddata, new byte[]{0x00,0x00});
662 | qddata = Util.byteMerger(qddata, Util.subByte(Util.ToByte(data.length), 2, 6));
663 | qddata = Util.byteMerger(qddata, new byte[]{0x00,0x00});
664 |
665 | user.TXProtocol.QdData = data;
666 | return data;
667 | }
668 |
669 |
670 | public static byte[] random_byte(int size)
671 | {
672 | byte [] b=new byte[size];
673 | Random random=new Random();
674 | random.nextBytes(b);
675 |
676 | return b;
677 | }
678 |
679 |
680 | public static short bytesToshort(byte[] input)
681 | {
682 | byte high = input[0];
683 | byte low = input[1];
684 | short z = (short)(((high & 0x00FF) << 8) | (0x00FF & low));
685 | return z;
686 |
687 |
688 | // TODO: Implement this metho
689 | }
690 |
691 | public static String GetIpStringFromBytes(byte[] input)
692 | {
693 | String u1 = String.valueOf((int)input[0] & 0xff);
694 | String u2 = String.valueOf((int)input[1] & 0xff);
695 | String u3 = String.valueOf((int)input[2] & 0xff);
696 | String u4 = String.valueOf((int)input[3] & 0xff);
697 | return u1 + "." + u2 + "." + u3 + "." + u4;
698 | }
699 |
700 | public static long bytesToLong(byte[] input, int offset, boolean littleEndian)
701 | {
702 | // 将byte[] 封装为 ByteBuffer
703 |
704 | ByteBuffer buffer = ByteBuffer.wrap(Util.byteMerger(new byte[8 - input.length], input));
705 |
706 | if (littleEndian)
707 | {
708 | // ByteBuffer.order(ByteOrder) 方法指定字节序,即大小端模式(BIG_ENDIAN/LITTLE_ENDIAN)
709 | // ByteBuffer 默认为大端(BIG_ENDIAN)模式
710 | buffer.order(ByteOrder.LITTLE_ENDIAN);
711 | }
712 |
713 | return buffer.getLong();
714 | }
715 |
716 | public static int GetInt(byte[] data, int offset, int length)
717 | {
718 | byte[] test = new byte[]{0x00,0x00,data[offset],data[offset + 1],0x00,0x00,0x00,0x00};
719 | ByteBuffer u = ByteBuffer.wrap(test);
720 |
721 | return u.getInt();
722 | }
723 |
724 | public static int GetInt(byte[] data)
725 | {
726 | byte[] test = new byte[]{0x00,0x00,data[0],data[1],0x00,0x00,0x00,0x00};
727 |
728 | ByteBuffer u = ByteBuffer.wrap(test);
729 |
730 | return u.getInt();
731 | }
732 |
733 | public static long GetLong(byte[] data)
734 | {
735 | byte[] test = new byte[]{0x00,0x00,0x00,0x00,data[0],data[1],data[2],data[3]};
736 |
737 | ByteBuffer u = ByteBuffer.wrap(test);
738 |
739 | return u.getLong();
740 | }
741 |
742 | public static short GetShort(byte[] data)
743 | {
744 | byte[] test = new byte[]{data[0],data[1],0x00,0x00,0x00,0x00,0x00,0x00};
745 |
746 | ByteBuffer u = ByteBuffer.wrap(test);
747 |
748 | return u.getShort();
749 | }
750 |
751 | public static Date GetDateTimeFromMillis(long timeMillis)
752 | {
753 | Date date = new Date(timeMillis);
754 | return date;
755 | }
756 |
757 | public static byte[] GetBytes(String string)
758 | {
759 |
760 |
761 | // TODO: Implement this method
762 | return string.getBytes();
763 | }
764 |
765 |
766 | public static String GET_GTK(String skey)
767 | {
768 | String arg = "tencentQQVIP123443safde&!%^%1282";
769 | List list = new ArrayList();
770 | int num = 5381;
771 | list.add(172192);
772 | int i = 0;
773 | for (int length = skey.length(); i < length; i++)
774 | {
775 | int num2 = (skey).charAt(i);
776 | list.add((num << 5) + num2);
777 | num = num2;
778 | }
779 |
780 | StringBuilder stringBuilder = new StringBuilder();
781 | for (i = 0; i < list.size(); i++)
782 | {
783 | stringBuilder.append(list.get(i).toString());
784 | }
785 |
786 | return Md5(stringBuilder + arg);
787 | }
788 | public static String Md5(String text)
789 | {
790 |
791 | try
792 | {
793 | MessageDigest md = MessageDigest.getInstance("MD5");
794 |
795 | byte[] bytes = md.digest(text.getBytes());
796 | String result = "";
797 | for (byte b : bytes)
798 | {
799 | result += String.format("%02x", b);
800 | }
801 |
802 | return result;
803 | }
804 | catch (NoSuchAlgorithmException e)
805 | {}
806 | return null;
807 | }
808 |
809 | public static byte[] MD5(String arg)
810 | {
811 | try
812 | {
813 | MessageDigest md = MessageDigest.getInstance("MD5");
814 |
815 |
816 | byte[] bytes = md.digest(arg.getBytes());
817 | return bytes;
818 | }
819 | catch (NoSuchAlgorithmException e)
820 | {}
821 | return null;
822 | }
823 | public static byte[] MD5(byte[] arg)
824 | {
825 | try
826 | {
827 | MessageDigest md = MessageDigest.getInstance("MD5");
828 |
829 |
830 | byte[] bytes = md.digest(arg);
831 | return bytes;
832 | }
833 | catch (NoSuchAlgorithmException e)
834 | {}
835 | return null;
836 | }
837 | public static String GetBkn(String skey)
838 | {
839 | int hash = 5381;
840 |
841 | for (int i = 0, len = skey.length(); i < len; ++i)
842 |
843 | hash += (hash << 5) + skey.charAt(i);
844 |
845 | int gtkOrbkn = hash & 2147483647;
846 |
847 | return String.valueOf(gtkOrbkn);
848 | }
849 |
850 | public static String ToHex(byte[] data, String p1, String p2)
851 | {
852 | String hex= "";
853 | if (data != null)
854 | {
855 | for (Byte b : data)
856 | {
857 | hex += String.format("%02X", b.intValue() & 0xFF);
858 | }
859 | }
860 | return hex;
861 | }
862 |
863 |
864 | public static String NumToHexString(int qq, int Length)
865 | {
866 |
867 | String text = String.valueOf(qq);
868 | if (text.length() == Length)
869 | {
870 | return text;
871 | }
872 |
873 | if (text.length() > Length)
874 | {
875 | return null;
876 | }
877 | return null;
878 | }
879 |
880 | public static byte[] byteMerger(List bytes)
881 | {
882 | int total_length = 0;
883 | int offset= 0;
884 | for (byte[] one : bytes)
885 | {
886 | total_length = total_length + one.length;
887 | }
888 | byte[] byte_3 = new byte[total_length];
889 |
890 | for (byte[] one_byte : bytes)
891 | {
892 | System.arraycopy(one_byte, 0, byte_3, offset, one_byte.length);
893 | offset = offset + one_byte.length;
894 | }
895 | return byte_3;
896 | }
897 |
898 |
899 | public static byte[] IPStringToByteArray(String ip)
900 | {
901 | byte[] array = new byte[4];
902 | String[] array2 = ip.split("[.]");
903 | if (array2.length == 4)
904 | {
905 | for (int i = 0; i < 4; i++)
906 | {
907 | array[i] = (byte) Integer.parseInt(array2[i]);
908 | }
909 | }
910 |
911 | return array;
912 | }
913 |
914 |
915 |
916 | public static byte[] RandomKey()
917 | {
918 | byte[] key = new byte[16];
919 | new Random().nextBytes(key);
920 | return key;
921 | }
922 | public static byte[] RandomKey(int size)
923 | {
924 | byte[] key = new byte[size];
925 | new Random().nextBytes(key);
926 | return key;
927 | }
928 |
929 | public static byte[] str_to_byte(String str)
930 | {
931 | String replaceAll = str.replaceAll(" ", "");
932 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(replaceAll.length() >> 1);
933 | for (int i = 0; i <= replaceAll.length() - 2; i += 2)
934 | {
935 | byteArrayOutputStream.write(Integer.parseInt(replaceAll.substring(i, i + 2), 16) & 255);
936 | }
937 | return byteArrayOutputStream.toByteArray();
938 | }
939 |
940 | public static byte[] byteMerger(byte[] byte_1, byte[] byte_2)
941 | {
942 | byte[] byte_3 = new byte[byte_1.length + byte_2.length];
943 | System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
944 | System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
945 | return byte_3;
946 | }
947 |
948 | public static byte[] ToByte(long x)
949 | {
950 | ByteBuffer buffer = ByteBuffer.allocate(8);
951 |
952 | buffer.putLong(x);
953 | return buffer.array();
954 | }
955 | public static byte[] ToByte(int x)
956 | {
957 | ByteBuffer buffer = ByteBuffer.allocate(8);
958 |
959 | buffer.putInt(x);
960 | return buffer.array();
961 | }
962 | public static byte[] ToByte(short x)
963 | {
964 | ByteBuffer buffer = ByteBuffer.allocate(8);
965 |
966 | buffer.putShort(x);
967 | return buffer.array();
968 | }
969 | public static String byte2HexString(byte[] bytes)
970 | {
971 | String hex= "";
972 | if (bytes != null)
973 | {
974 | for (Byte b : bytes)
975 | {
976 | hex += String.format("%02X", b.intValue() & 0xFF) + " ";
977 | }
978 | }
979 | return hex;
980 |
981 | }
982 |
983 | public static byte[] subByte(byte[] b, int off, int length)
984 | {
985 |
986 | if (b.length != 0 && b != null)
987 | {
988 | byte[] b1 = new byte[length];
989 | System.arraycopy(b, off, b1, 0, length);
990 | return b1;
991 | }
992 | return new byte[1];
993 | }
994 |
995 | public static class miTM implements TrustManager,X509TrustManager
996 | {
997 |
998 | @Override
999 | public void checkServerTrusted(X509Certificate[] p1, String p2) throws CertificateException
1000 | {
1001 | }
1002 |
1003 | @Override
1004 | public X509Certificate[] getAcceptedIssuers()
1005 | {
1006 | return null;
1007 | }
1008 |
1009 | public static boolean isServerTrusted(X509Certificate[] certs)
1010 | {
1011 | return true;
1012 | }
1013 |
1014 | public static boolean isClientTrusted(X509Certificate[] certs)
1015 | {
1016 | return true;
1017 | }
1018 |
1019 | @Override
1020 | public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException
1021 | {
1022 | }
1023 | }
1024 | }
1025 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/Utils/ZLibUtils.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.Utils;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 | import java.util.zip.Deflater;
8 | import java.util.zip.DeflaterOutputStream;
9 | import java.util.zip.Inflater;
10 | import java.util.zip.InflaterInputStream;
11 |
12 | /**
13 | * ZLib压缩工具
14 | *
15 | * @author 梁栋
16 | * @version 1.0
17 | * @since 1.0
18 | */
19 | public class ZLibUtils {
20 |
21 | /**
22 | * 压缩
23 | *
24 | * @param data
25 | * 待压缩数据
26 | * @return byte[] 压缩后的数据
27 | */
28 | public static byte[] compress(byte[] data) {
29 | byte[] output = new byte[0];
30 |
31 | Deflater compresser = new Deflater();
32 |
33 | compresser.reset();
34 | compresser.setInput(data);
35 | compresser.finish();
36 | ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
37 | try {
38 | byte[] buf = new byte[1024];
39 | while (!compresser.finished()) {
40 | int i = compresser.deflate(buf);
41 | bos.write(buf, 0, i);
42 | }
43 | output = bos.toByteArray();
44 | } catch (Exception e) {
45 | output = data;
46 | e.printStackTrace();
47 | } finally {
48 | try {
49 | bos.close();
50 | } catch (IOException e) {
51 | e.printStackTrace();
52 | }
53 | }
54 | compresser.end();
55 | return output;
56 | }
57 |
58 | /**
59 | * 压缩
60 | *
61 | * @param data
62 | * 待压缩数据
63 | *
64 | * @param os
65 | * 输出流
66 | */
67 | public static void compress(byte[] data, OutputStream os) {
68 | DeflaterOutputStream dos = new DeflaterOutputStream(os);
69 |
70 | try {
71 | dos.write(data, 0, data.length);
72 |
73 | dos.finish();
74 |
75 | dos.flush();
76 | } catch (IOException e) {
77 | e.printStackTrace();
78 | }
79 | }
80 |
81 | /**
82 | * 解压缩
83 | *
84 | * @param data
85 | * 待压缩的数据
86 | * @return byte[] 解压缩后的数据
87 | */
88 | public static byte[] decompress(byte[] data) {
89 | byte[] output = new byte[0];
90 |
91 | Inflater decompresser = new Inflater();
92 | decompresser.reset();
93 | decompresser.setInput(data);
94 |
95 | ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);
96 | try {
97 | byte[] buf = new byte[1024];
98 | while (!decompresser.finished()) {
99 | int i = decompresser.inflate(buf);
100 | o.write(buf, 0, i);
101 | }
102 | output = o.toByteArray();
103 | } catch (Exception e) {
104 | output = data;
105 | e.printStackTrace();
106 | } finally {
107 | try {
108 | o.close();
109 | } catch (IOException e) {
110 | e.printStackTrace();
111 | }
112 | }
113 |
114 | decompresser.end();
115 | return output;
116 | }
117 |
118 | /**
119 | * 解压缩
120 | *
121 | * @param is
122 | * 输入流
123 | * @return byte[] 解压缩后的数据
124 | */
125 | public static byte[] decompress(InputStream is) {
126 | InflaterInputStream iis = new InflaterInputStream(is);
127 | ByteArrayOutputStream o = new ByteArrayOutputStream(1024);
128 | try {
129 | int i = 1024;
130 | byte[] buf = new byte[i];
131 |
132 | while ((i = iis.read(buf, 0, i)) > 0) {
133 | o.write(buf, 0, i);
134 | }
135 |
136 | } catch (IOException e) {
137 | e.printStackTrace();
138 | }
139 | return o.toByteArray();
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/API.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 | import android.graphics.*;
3 | import android.content.Context;
4 |
5 | public interface API
6 | {
7 | public void SendGroupTextMessage(long gin,String msg);
8 | public void SendGroupXMLMessage(long gin,String xml);
9 | public void SendGroupBitmapMessage(long gin,Bitmap bitmap);
10 | public void SendFriendTextMessage(long uin,String msg);
11 | public void SendFriendXMLMessage(long uin,String xml);
12 | public void GroupMemberShutUp(long gin,long uin,int time);
13 | public void setGroupMemberCard(long gin,long uin,String card);
14 | public Context getContext();
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/Plugin.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 |
3 | public interface Plugin
4 | {
5 | String author();
6 | String Version();
7 | String name();
8 |
9 | void onLoad(API api);
10 |
11 | void onMessageHandler(QQMessage message);
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/QQMessage.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 | import java.util.*;
3 |
4 | public class QQMessage
5 | {
6 | public int Message_Type = 0;
7 | public long Sender_Uin = 0;
8 | public long Message_Time = 0;
9 | public long Message_Id =0;
10 |
11 | public byte[] MessageIndex;
12 |
13 | public long Reciece_Message_Time;
14 |
15 | public long Send_Message_Time;
16 |
17 | public String Message_Font;
18 |
19 | public String Message = "";
20 |
21 | public String Message_Text;
22 |
23 | public boolean Isat;
24 |
25 | public List Atlist = new ArrayList();
26 |
27 | public String SendName;
28 |
29 | public long Group_uin;
30 |
31 | public long Self_uin;
32 |
33 | public int contain_type =0;
34 |
35 | public QQMessage(){
36 |
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/net/newlydev/qqrobot/mApplication.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot;
2 | import android.app.*;
3 | import net.newlydev.qqrobot.PCTIM.Utils.*;
4 | import android.content.Context;
5 |
6 | public class mApplication extends Application
7 | {
8 | private static mApplication mApp;
9 |
10 | public static Context getContext()
11 | {
12 | return mApp.getApplicationContext();
13 | }
14 | @Override
15 | public void onCreate()
16 | {
17 | // TODO: Implement this method
18 | super.onCreate();
19 | mApp=this;
20 | try
21 | {
22 | Util.trustAllHttpsCertificates();
23 | }
24 | catch (Exception e)
25 | {}
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/app/src/main/res/drawable/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
23 |
24 |
29 |
30 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | QQRobot
5 | Please wait...
6 | As a plugin of qqrobot"
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.6.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.3"
6 |
7 | defaultConfig {
8 | applicationId "net.newlydev.qqrobot.demo"
9 | minSdkVersion 14
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(dir: 'libs', include: ['*.jar'])
28 |
29 | testImplementation 'junit:junit:4.12'
30 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
32 | }
33 |
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/demo/src/androidTest/java/net/newlydev/qqrobot/demo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.demo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("net.newlydev.qqrobot.demo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/demo/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/API.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 | import android.graphics.*;
3 | import android.content.Context;
4 |
5 | public interface API
6 | {
7 | public void SendGroupTextMessage(long gin,String msg);
8 | public void SendGroupXMLMessage(long gin,String xml);
9 | public void SendGroupBitmapMessage(long gin,Bitmap bitmap);
10 | public void SendFriendTextMessage(long uin,String msg);
11 | public void SendFriendXMLMessage(long uin,String xml);
12 | public void GroupMemberShutUp(long gin,long uin,int time);
13 | public void setGroupMemberCard(long gin,long uin,String card);
14 | public Context getContext();
15 | }
16 |
--------------------------------------------------------------------------------
/demo/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/Plugin.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 |
3 | public interface Plugin
4 | {
5 | String author();
6 | String Version();
7 | String name();
8 |
9 | void onLoad(API api);
10 |
11 | void onMessageHandler(QQMessage message);
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/demo/src/main/java/net/newlydev/qqrobot/PCTIM/sdk/QQMessage.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.PCTIM.sdk;
2 | import java.util.*;
3 |
4 | public class QQMessage
5 | {
6 | public int Message_Type = 0;
7 | public long Sender_Uin = 0;
8 | public long Message_Time = 0;
9 | public long Message_Id =0;
10 |
11 | public byte[] MessageIndex;
12 |
13 | public long Reciece_Message_Time;
14 |
15 | public long Send_Message_Time;
16 |
17 | public String Message_Font;
18 |
19 | public String Message = "";
20 |
21 | public String Message_Text;
22 |
23 | public boolean Isat;
24 |
25 | public List Atlist = new ArrayList();
26 |
27 | public String SendName;
28 |
29 | public long Group_uin;
30 |
31 | public long Self_uin;
32 |
33 | public int contain_type =0;
34 |
35 | public QQMessage(){
36 |
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/demo/src/main/java/net/newlydev/qqrobot/demo/Main.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.demo;
2 |
3 | import net.newlydev.qqrobot.PCTIM.sdk.API;
4 | import net.newlydev.qqrobot.PCTIM.sdk.Plugin;
5 | import net.newlydev.qqrobot.PCTIM.sdk.QQMessage;
6 |
7 | public class Main implements Plugin {
8 | private API api;
9 | @Override
10 | public String author() {
11 | return null;
12 | }
13 |
14 | @Override
15 | public String Version() {
16 | return null;
17 | }
18 |
19 | @Override
20 | public String name() {
21 | return null;
22 | }
23 |
24 | @Override
25 | public void onLoad(API api) {
26 | this.api=api;
27 | }
28 |
29 | @Override
30 | public void onMessageHandler(QQMessage message) {
31 | if("test".equals(message.Message))
32 | {
33 | api.SendGroupTextMessage(message.Group_uin,"ok");
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PluginDemo
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/test/java/net/newlydev/qqrobot/demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package net.newlydev.qqrobot.demo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uebian/QQRobot/5f38aba8224f2f521f62198b70b354544a8172eb/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Mar 24 11:26:13 UTC 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':demo'
3 |
--------------------------------------------------------------------------------