├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── pymirai.iml ├── AndroidQQ.py ├── LICENSE ├── README.md ├── datas ├── main.py ├── pymirai ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── __init__.cpython-38.pyc ├── binary │ └── __init__.py ├── client │ ├── __init__.py │ ├── client.py │ ├── entities.py │ └── global_.py ├── net │ ├── __init__.py │ └── tcp.py ├── protocol │ └── __init__.py ├── signals.py └── utils │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-38.pyc │ ├── pack.cpython-37.pyc │ ├── pack.cpython-38.pyc │ ├── tea.cpython-37.pyc │ ├── tea.cpython-38.pyc │ ├── tlv.cpython-37.pyc │ ├── tools.cpython-37.pyc │ ├── tools.cpython-38.pyc │ └── unpack.cpython-37.pyc │ ├── pack.py │ ├── tea.py │ ├── tlv.py │ ├── tools.py │ └── unpack.py └── tests ├── data.txt ├── test_login.py ├── test_pack.py └── test_signal.py /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 数据源本地存储已忽略文件 5 | /../../../../:\jhc\pymirai\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # 基于编辑器的 HTTP 客户端请求 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/pymirai.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AndroidQQ.py: -------------------------------------------------------------------------------- 1 | from pymirai.utils.pack import Pack 2 | from pymirai.utils.unpack import Unpack 3 | from pymirai.utils.tlv import Tlv 4 | from pymirai.utils.tea import Tea 5 | from pymirai.utils.tools import * 6 | import time 7 | import hashlib 8 | 9 | class AndroidQQ(object): 10 | def __init__(self,qq,password,xy): 11 | self.qq = qq 12 | self.password = password 13 | self.xy = xy #协议 14 | self.starttime = int(time.time()) 15 | self.requestId = 10000 16 | self.tgtkey = getRandomBin(16) 17 | self.sharekey = hex2bytes('4A ED 5E CF F6 19 92 A8 BB 62 B3 A8 B3 C4 B0 8E') 18 | self.publickey = hex2bytes('04 5F FB B8 6D 00 A3 7F A9 9B 6A DB 6B C5 B1 75 B3 DD 51 5A FF 66 F6 04 76 85 BA 7F 66 69 69 D8 72 6F 4E 8F 40 B6 EC 17 80 F0 64 A5 51 2F 2B AD 18 5C C2 50 A9 4E BB 25 49 E4 D0 65 54 F9 66 0F A0') 19 | self.privatekey = hex2bytes('00 00 00 21 00 94 C7 25 8B 78 45 33 AB 23 73 B4 3A 60 AB 37 1D D4 53 3B 5A BD FB D6 43 C7 A2 3F CB 5A 08 01 A5') 20 | self.msgCookies = bytes() 21 | 22 | m = hashlib.md5() 23 | m.update(password.encode(encoding='utf-8')) 24 | self.md5pass = m.digest() 25 | m = hashlib.md5() 26 | m.update(self.md5pass + bytes(4) + int2bytes(int(self.qq),4)) 27 | self.md52pass = m.digest() 28 | 29 | self.deviceguid = 'C3 6D 03 70 6B 7C 4E DD C0 77 46 91 C1 FB 91 F8' 30 | self.devicename = 'oppo r9 plustm a' 31 | self.devicebrand = 'oppo' 32 | self.deviceMac = '54 44 61 90 FC 9C 7E 08 C4 13 59 26 B8 73 4B C2' 33 | self.deviceImsi = '460001330114682' 34 | self.deviceimie = '865166024867445' 35 | self.ver = '|' + self.deviceImsi + '|A8.4.10.b8c39faf' 36 | self.bssid = '' 37 | self.ssid = 'dlb' 38 | self.AndroidId = 'CC 3C DD 51 8A 92 6C 6C 54 FF 46 48 CE E2 1D 29' 39 | self.appid = 537065990 40 | self.appid2 = 537065990 41 | self.main_signmap = 34869472 42 | self.apk_v = '8.4.10' 43 | self.apk_sig = 'A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D' 44 | self.apkid = 'com.tencent.mobileqq' 45 | self.sdkversion = '6.0.0.2438' 46 | 47 | def Pack_Login(self): 48 | pack = Pack() 49 | pack.write_short(9) 50 | pack.write_short(23) #23个tlv 51 | pack.write_bytes(Tlv.tlv018(self.qq)) 52 | pack.write_bytes(Tlv.tlv001(self.qq,self.starttime)) 53 | pack.write_bytes(Tlv.tlv106(self.qq,self.md5pass,self.md52pass,self.tgtkey,self.deviceguid,self.starttime,self.appid)) 54 | pack.write_bytes(Tlv.tlv116(3)) 55 | pack.write_bytes(Tlv.tlv100(self.appid,self.main_signmap)) 56 | pack.write_bytes(Tlv.tlv107()) 57 | pack.write_bytes(Tlv.tlv142(self.apkid)) 58 | pack.write_bytes(Tlv.tlv144(self.tgtkey,Tlv.tlv109(self.AndroidId),Tlv.tlv124(),Tlv.tlv128(self.devicename,self.devicebrand,self.deviceguid),Tlv.tlv16E(self.devicename))) 59 | pack.write_bytes(Tlv.tlv145(self.deviceguid)) 60 | pack.write_bytes(Tlv.tlv147(self.apk_v,self.apk_sig)) 61 | pack.write_bytes(Tlv.tlv154(self.requestId)) 62 | pack.write_bytes(Tlv.tlv141()) 63 | pack.write_bytes(Tlv.tlv008()) 64 | pack.write_bytes(Tlv.tlv511()) 65 | pack.write_bytes(Tlv.tlv187(self.deviceMac)) 66 | pack.write_bytes(Tlv.tlv188(self.deviceMac)) 67 | pack.write_bytes(Tlv.tlv194(self.deviceImsi)) 68 | pack.write_bytes(Tlv.tlv191()) 69 | pack.write_bytes(Tlv.tlv202(self.bssid,self.ssid)) 70 | pack.write_bytes(Tlv.tlv177(self.starttime,self.sdkversion)) 71 | pack.write_bytes(Tlv.tlv516()) 72 | pack.write_bytes(Tlv.tlv521()) 73 | pack.write_bytes(Tlv.tlv525()) 74 | 75 | pkt = Tea.encrypt(pack.get_all(),self.sharekey) 76 | pkt = self.Pack_LoginHead(pkt,0) 77 | pkt = self.Pack_Head(pkt,1) 78 | return pkt 79 | 80 | def Pack_LoginHead(self,pkt,mtype): 81 | pack = Pack() 82 | pack.write_int(self.requestId) 83 | pack.write_int(self.appid) 84 | pack.write_int(self.appid2) 85 | pack.write_hex('01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 04') 86 | pack.write_int(17) 87 | pack.write_str('wtlogin.login') 88 | pack.write_hex('00 00 00 08') 89 | pack.write_bytes(getRandomBin(4)) 90 | pack.write_int(len(self.deviceimie)+4) 91 | pack.write_str(self.deviceimie) 92 | if mtype == 2: 93 | pack.write_hex('00 00 00 14') 94 | pack.write_bytes(getRandomBin(16)) 95 | else: 96 | pack.write_hex('00 00 00 04') 97 | pack.write_short(len(self.ver)+2) 98 | pack.write_str(self.ver) 99 | if self.xy == 1: 100 | pack.write_hex('00 00 00 04') 101 | else: 102 | pack.write_hex('00 00 00 2A') 103 | pack.write_hex('62 24 31 65 62 63 38 35 64 65 37 33 36 35 64 65 34 64 31 35 35 63 65 34 30 31 31 30 30 30 31 35 38 31 34 37 31 64') 104 | 105 | headpkt = pack.get_all() 106 | pack.set_empty() 107 | pack.write_int(len(headpkt)+4) 108 | pack.write_bytes(headpkt) 109 | headpkt = pack.get_all() 110 | 111 | pack.set_empty() 112 | pack.write_hex('1F 41 08 10 00 01') 113 | pack.write_int(int(self.qq)) 114 | if self.xy == 1: 115 | pack.write_hex('03 87 00 00 00 00 02 00 00 00 00 00 00 00 00 01 01') 116 | elif mtype == 0: 117 | pack.write_hex('03 87 00 00 00 00 02 00 00 00 00 00 00 00 00 02 01') 118 | else: 119 | pack.write_hex('03 07 00 00 00 00 02 00 00 00 00 00 00 00 00 02 01') 120 | pack.write_bytes(getRandomBin(16)) 121 | if self.xy == 1: 122 | pack.write_hex('01 02') 123 | elif mtype == 2: 124 | pack.write_hex('01 31 00 02') 125 | else: 126 | pack.write_hex('01 31 00 01') 127 | pack.write_short(len(self.publickey)) 128 | pack.write_bytes(self.publickey) 129 | pack.write_bytes(pkt) 130 | 131 | pkt = pack.get_all() 132 | 133 | pack.set_empty() 134 | pack.write_hex('02') 135 | pack.write_short(len(pkt)+4) 136 | pack.write_bytes(pkt) 137 | pack.write_hex('03') 138 | 139 | pkt = pack.get_all() 140 | 141 | pack.set_empty() 142 | pack.write_bytes(headpkt) 143 | pack.write_int(len(pkt)+4) 144 | pack.write_bytes(pkt) 145 | 146 | pkt = Tea.encrypt(pack.get_all(),bytes(16)) 147 | return pkt 148 | 149 | def Pack_Head(self,pkt,mtype): 150 | pack = Pack() 151 | if mtype == 1: 152 | pack.write_hex('00 00 00 0A 02 00 00 00 04') 153 | elif mtype == 2: 154 | pass 155 | elif mtype == 3: 156 | pack.write_hex('00 00 00 0B 01') 157 | pack.write_int(self.requestId) 158 | else: 159 | pack.write_hex('00 00 00 0B 02') 160 | pack.write_int(self.requestId) 161 | pack.write_hex('00 00 00') 162 | pack.write_short(len(self.qq)+4) 163 | pack.write_str(self.qq) 164 | pack.write_bytes(pkt) 165 | 166 | pkt = pack.get_all() 167 | 168 | pack.set_empty() 169 | pack.write_int(len(pkt)+4) 170 | pack.write_bytes(pkt) 171 | 172 | pkt = pack.get_all() 173 | return pkt 174 | 175 | def Unpack_Login(self,pkt:bytes): 176 | position = pkt.find(str2bytes(self.qq)) 177 | pkt = pkt[position+len(self.qq):] 178 | pkt = Tea.decrypt(pkt,bytes(16)) 179 | up = Unpack(pkt) 180 | headlen = up.getInt() 181 | headdata = up.getBin(headlen - 4) 182 | maindata = up.getAll() 183 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The mirai implemention on python 2 | 3 | ## 在大佬的帮助下凑齐了工具类,总算可以对付字节流了 4 | - ~~py的字节流处理啊……简直像隔着裤子撸管~~ 5 | 6 | 7 | - [x] 下一步解包登录 8 | -------------------------------------------------------------------------------- /datas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/datas -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from socket import socket,AF_INET,SOCK_STREAM 2 | from pymirai.utils.tools import * 3 | from pymirai.utils.pack import Pack 4 | from pymirai.utils.tea import Tea 5 | import time 6 | from AndroidQQ import AndroidQQ 7 | #print(bytes2hex(int2bytes(2193096276,4))) 8 | Loginqq = AndroidQQ('你的名字','你的密码',0) 9 | #print(bytes2hex(Loginqq.Pack_Login())) 10 | s=socket(AF_INET,SOCK_STREAM) 11 | s.connect(("113.96.12.224",8080)) 12 | s.send(Loginqq.Pack_Login()) 13 | buf = s.recv(2048) 14 | s.close() 15 | #print(bytes2hex(buf)) 16 | Loginqq.Unpack_Login(buf) 17 | 18 | # qq='2193096276' 19 | # pack = Pack() 20 | # pack.setShort(len(qq)) 21 | # pack.setStr(qq) 22 | # pack.setHex('00 04') 23 | # pack.setInt(int(qq)) 24 | # print(Str2Bytes('哈哈哈')) 25 | # print(Bytes2Hex(pack.getAll())) 26 | 27 | # a = Bytes2Hex(tea.encrypt(Str2Bytes('哈哈哈'),Hex2Bytes('12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12'))) 28 | # print(a) 29 | # b = Bytes2Hex(tea.decrypt(Hex2Bytes(a),Hex2Bytes('12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12'))) 30 | # print(b) 31 | -------------------------------------------------------------------------------- /pymirai/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def main(): 4 | pass 5 | 6 | 7 | if __name__ == "__main__": 8 | main() 9 | -------------------------------------------------------------------------------- /pymirai/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /pymirai/binary/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | jce 序列化相关 4 | """ 5 | 6 | 7 | def main(): 8 | pass 9 | 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /pymirai/client/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def main(): 4 | pass 5 | 6 | 7 | if __name__ == "__main__": 8 | main() 9 | -------------------------------------------------------------------------------- /pymirai/client/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | from https://github.com/Mrs4s/MiraiGo/blob/94779a6b765bf1acd23bd99fa1cbf1b2ff6fa75a/client/client.go 4 | """ 5 | from typing import List 6 | import ipaddress 7 | 8 | from pydantic import BaseModel 9 | 10 | from pymirai.client.entities import FriendInfo, GroupInfo 11 | from pymirai.client.global_ import VersionInfo 12 | from pymirai.net import TCPConnection 13 | from pymirai.signals import SignalManager 14 | 15 | 16 | class QQClient(BaseModel): 17 | """ 18 | qq客户端 sms是短信验证码 19 | """ 20 | uin: int 21 | password_md5: str 22 | allow_slider: bool 23 | 24 | nickname: str 25 | age: int 26 | gender: int 27 | friend_list: List[FriendInfo] 28 | group_list: List[GroupInfo] 29 | online: bool 30 | net_looping: bool 31 | 32 | sequence_id: int 33 | outgoing_packet_session_id: bytes 34 | random_key: bytes 35 | conn: TCPConnection # tcp连接? 36 | connect_time: int 37 | 38 | handlers: SignalManager # 这里跟go不一样 ,用signal订阅发布模式在收到数据后广播信号 ,函数来处理 39 | servers: List[ipaddress.IPv4Address] 40 | curr_server_index: int 41 | retry_times: int 42 | version: VersionInfo 43 | 44 | sync_cookie: bytes 45 | pub_account_cookie: bytes 46 | msg_ctrl_buf: bytes 47 | ksid: bytes # 登录包 48 | t104: bytes 49 | t174: bytes 50 | t402: bytes 51 | t150: bytes 52 | t149: bytes 53 | t528: bytes 54 | t530: bytes 55 | rollback_sig: bytes 56 | time_diff: int 57 | sig_info: "LoginSigInfo" # TODO 抄go的 58 | pwd_flag: bool 59 | last_message_seq: int 60 | 61 | 62 | class LoginSigInfo(BaseModel): 63 | pass 64 | -------------------------------------------------------------------------------- /pymirai/client/entities.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | from https://github.com/Mrs4s/MiraiGo/blob/94779a6b765bf1acd23bd99fa1cbf1b2ff6fa75a/client/entities.go#L43 4 | """ 5 | import asyncio 6 | from typing import List 7 | 8 | from pydantic import BaseModel 9 | 10 | from .client import QQClient 11 | 12 | NeedCaptcha = 1 13 | OtherLoginError = 3 14 | UnsafeDeviceError = 4 15 | SMSNeededError = 5 16 | TooManySMSRequestError = 6 17 | SMSOrVerifyNeededError = 7 18 | SliderNeededError = 8 19 | UnknownLoginError = -1 20 | 21 | Owner = 0 22 | Administrator = 1 23 | Member = 2 24 | 25 | AndroidPhone = 1 26 | IPad = 2 27 | AndroidWatch = 3 28 | MacOS = 4 29 | 30 | 31 | class LoginResponse(BaseModel): 32 | success: bool 33 | error: int 34 | # Captcha info 35 | captcha_image: bytes 36 | captcha_sign: bytes 37 | # Unsafe device 38 | verify_url: str 39 | # SMS needed 40 | sms_phone: str 41 | # other error 42 | error_message: str 43 | 44 | 45 | class FriendInfo(BaseModel): 46 | uin: int 47 | nike_name: str 48 | remark: str 49 | face_id: int 50 | 51 | 52 | class FriendListResponse(BaseModel): 53 | total_count: int 54 | list_: List[FriendInfo] 55 | 56 | 57 | class SummaryCardInfo(BaseModel): 58 | uin: int 59 | sex: bytes 60 | age: int 61 | nick_name: str 62 | level: int 63 | city: str 64 | sign: str 65 | mobile: str 66 | login_days: int 67 | 68 | 69 | class GroupInfo(BaseModel): 70 | uin: int 71 | code: int 72 | name: str 73 | memo: str 74 | owner_uin: int 75 | member_count: int 76 | max_member_count: int 77 | members: List["GroupMemberInfo"] 78 | client: QQClient 79 | last_msg_seq: int 80 | lock: asyncio.Lock # 我们为什么要锁?我们是高贵的py,有gil的 昵昵说,消息异步处理,可能修改members,因此要加读写锁.那就加上吧 81 | 82 | 83 | class GroupMemberInfo(BaseModel): 84 | group: GroupInfo 85 | uin: int 86 | gender: bytes 87 | nick_name: str 88 | card_name: str 89 | level: int 90 | join_time: int 91 | last_speak_time: int 92 | special_title: str 93 | special_title_expire_time: int # 震惊:特殊头衔居然会过期 94 | permission: int # go里面是MemberPermission int emmm 要不变枚举? 95 | 96 | 97 | class GroupMuteEvent(BaseModel): 98 | group_code: int 99 | operator_uin: int 100 | target_uin: int 101 | time: int 102 | 103 | 104 | class GroupMessageRecalledEvent(BaseModel): 105 | group_code: int 106 | operator_uin: int 107 | author_uin: int 108 | message_id: int 109 | time: int 110 | 111 | 112 | class FriendMessageRecalledEvent(BaseModel): 113 | friend_uin: int 114 | message_id: int 115 | time: int 116 | 117 | 118 | class GroupLeaveEvent(BaseModel): 119 | group: GroupInfo 120 | operator: GroupMemberInfo 121 | 122 | 123 | class MemberJoinGroupEvent(BaseModel): 124 | group: GroupInfo 125 | member: GroupMemberInfo 126 | 127 | 128 | class MemberCardUpdatedEvent(BaseModel): 129 | group: GroupInfo 130 | old_card: str 131 | member: GroupMemberInfo 132 | 133 | 134 | # TODO 这里有个接口 暂时作用不明确 135 | 136 | class MemberLeaveGroupEvent(BaseModel): 137 | group: GroupInfo 138 | member: GroupMemberInfo 139 | operator: GroupMemberInfo 140 | 141 | 142 | class MemberPermissionChangedEvent(BaseModel): 143 | group: GroupInfo 144 | member: GroupMemberInfo 145 | old_permission: int 146 | new_permission: int 147 | 148 | 149 | class ClientDisconnectedEvent(BaseModel): 150 | message: str 151 | 152 | 153 | class NewFriendRequest(BaseModel): 154 | request_id: int 155 | message: str 156 | requester_uin: int 157 | requester_nick: int 158 | client: QQClient 159 | 160 | 161 | class LogEvent(BaseModel): 162 | type_: str 163 | message: str 164 | 165 | 166 | class ServerUpdatedEvent(BaseModel): 167 | """ 168 | 服务器ip换了 TODO 抄这个得看pb了先放放 169 | """ 170 | pass 171 | 172 | 173 | class NewFriendEvent(BaseModel): 174 | friend: FriendInfo 175 | 176 | 177 | class OfflineFileEvent(BaseModel): 178 | file_name: str 179 | file_size: int 180 | sender: int 181 | download_url: str 182 | 183 | 184 | class OcrResponse(BaseModel): 185 | texts: List["TextDetection"] # TODO 看go的ocr实现 186 | language: str 187 | 188 | 189 | class TextDetection(BaseModel): 190 | text: str 191 | confidence: int 192 | coordinates: List["Coordinate"] # 坐标 193 | 194 | 195 | class Coordinate(BaseModel): 196 | x: int 197 | y: int 198 | 199 | 200 | class GroupMemberListResponse(BaseModel): 201 | next_uin: int 202 | list_: List[GroupMemberInfo] 203 | 204 | 205 | class ImageUploadResponse(BaseModel): 206 | pass 207 | 208 | # TODO 抄到206了 不写了 209 | -------------------------------------------------------------------------------- /pymirai/client/global_.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | from /client/global.go 4 | """ 5 | from pydantic import BaseModel 6 | 7 | 8 | class DeviceInfo(BaseModel): 9 | display: bytes 10 | product: bytes 11 | device: bytes 12 | board: bytes 13 | brand: bytes 14 | model: bytes 15 | boot_loader: bytes 16 | finger_print: bytes 17 | boot_id: bytes 18 | proc_version: bytes 19 | base_band: bytes 20 | sim_info: bytes 21 | os_type: bytes 22 | mac_address: bytes 23 | ipaddress: bytes 24 | wifi_bssid: bytes 25 | wifi_ssid: bytes 26 | imsi_md5: bytes 27 | imei: str 28 | android_id: bytes 29 | apn: bytes 30 | guid: bytes 31 | tgtgt_key: bytes 32 | protocol: int 33 | version: "Version" 34 | 35 | 36 | class Version(BaseModel): 37 | incremental: bytes 38 | release: bytes 39 | code_name: bytes 40 | sdk: int 41 | 42 | 43 | class DeviceInfoFile(BaseModel): 44 | display: str 45 | product: str 46 | device: str 47 | board: str 48 | model: str 49 | fingerprint: str 50 | boot_id: str 51 | proc_version: str 52 | protocol: int # 0: Pad 1: Phone 2: Watch 3: Mac 53 | imei: str 54 | 55 | 56 | class GroupMessageBuilder(BaseModel): 57 | """ 58 | TODO 留给pb 59 | """ 60 | 61 | 62 | class VersionInfo(BaseModel): 63 | apk_sign: bytes 64 | apk_id: str 65 | sort_version_name: str 66 | sdk_version: str 67 | app_id: int 68 | build_time: int 69 | sso_version: int 70 | misc_bitmap: int 71 | sub_sigmap: int 72 | main_sigmap: int 73 | 74 | -------------------------------------------------------------------------------- /pymirai/net/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .tcp import TCPConnection 3 | -------------------------------------------------------------------------------- /pymirai/net/tcp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import asyncio 3 | import socket 4 | 5 | CHUNK_SIZE = -1 6 | 7 | 8 | class TCPConnection: 9 | """ 10 | 对流的封装 方便处理 11 | """ 12 | 13 | def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): 14 | self.buffer = b"" 15 | self.closed = False 16 | self.reader = reader 17 | self.writer = writer 18 | self.socket: socket.socket = writer.get_extra_info("socket") 19 | 20 | def __str__(self): 21 | return f"TCPConnection to {self.peername}" 22 | 23 | @classmethod 24 | async def new(cls, host: str, port: int, **kw) -> "TCPConnection": 25 | reader, writer = await asyncio.open_connection(host, port, **kw) 26 | return cls(reader, writer) 27 | 28 | @property 29 | def peername(self): 30 | return self.writer.get_extra_info('peername') 31 | 32 | def add_into_buffer(self, data: bytes) -> None: 33 | self.buffer += data 34 | 35 | def recv(self, size: int = CHUNK_SIZE) -> bytes: 36 | return self.socket.recv(size) 37 | 38 | def send(self, data: bytes) -> int: 39 | return self.socket.send(data) 40 | 41 | async def read(self, size: int = CHUNK_SIZE) -> bytes: 42 | return await self.reader.read(size) 43 | 44 | def write(self, data: bytes) -> None: 45 | self.writer.write(data) 46 | 47 | async def drain(self) -> None: 48 | await self.writer.drain() 49 | 50 | async def flush(self): 51 | self.write(self.buffer) 52 | self.buffer = b"" 53 | await self.drain() 54 | 55 | async def close(self): 56 | self.writer.close() 57 | await self.writer.wait_closed() 58 | self.closed = True 59 | -------------------------------------------------------------------------------- /pymirai/protocol/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 组包的工具 4 | """ 5 | 6 | 7 | def main(): 8 | pass 9 | 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /pymirai/signals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 接收到数据的时候广播信号,调用函数来处理 对,我抄了scrapy 4 | """ 5 | import asyncio 6 | from typing import Callable, Any, List, Tuple 7 | 8 | from pydispatch import dispatcher 9 | 10 | 11 | class SignalManager: 12 | def __init__(self, sender=dispatcher.Anonymous): 13 | self.sender = sender 14 | 15 | def connect(self, receiver: Callable, signal: Any, **kwargs) -> None: 16 | """ 17 | 注册一个信号的响应函数 18 | :param receiver: 响应函数 19 | :param signal: 信号对象 20 | :param kwargs: 21 | :return: 22 | """ 23 | return dispatcher.connect(receiver, signal, self.sender, **kwargs) 24 | 25 | def disconnect(self, receiver: Callable, signal, **kwargs) -> None: 26 | """ 27 | 断开一个接收器的对于给定信号的连接 28 | :param receiver: 29 | :param signal: 30 | :param kwargs: 31 | :return: 32 | """ 33 | return dispatcher.disconnect(receiver, signal, self.sender, **kwargs) 34 | 35 | def disconnect_all(self, signal, **kwargs) -> None: 36 | """ 37 | 将所有接收器断开给定信号的连接 38 | :param signal: 39 | :param kwargs: 40 | :return: 41 | """ 42 | for receiver in dispatcher.liveReceivers(dispatcher.getAllReceivers(self.send, signal)): 43 | dispatcher.disconnect(receiver, signal, self.sender, **kwargs) 44 | 45 | def send(self, signal, *args, **kwargs) -> List[Tuple]: 46 | """ 47 | 48 | :param signal: 49 | :param kwargs: 50 | :return: list of tuple pairs [(receiver, response), ... ] 51 | """ 52 | kwargs.setdefault('sender', self.sender) 53 | return dispatcher.send(signal, self.sender, *args, **kwargs) 54 | 55 | async def send_async(self, signal, *args, **kwargs) -> List[Tuple]: 56 | """ 57 | receiver都是协程的情况下使用 58 | :param signal: 59 | :param kwargs: 60 | :return: list of tuple pairs [(receiver, response), ... ] 61 | """ 62 | 63 | async def _process_data(data_: List[Tuple]): 64 | ret = await asyncio.gather(*map(lambda x: x[1], data_)) 65 | return list(zip(map(lambda x: x[0], data_), ret)) 66 | 67 | data = dispatcher.send(signal, self.sender, *args, **kwargs) 68 | return await _process_data(data) 69 | -------------------------------------------------------------------------------- /pymirai/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def main(): 4 | pass 5 | 6 | 7 | if __name__ == "__main__": 8 | main() 9 | -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/pack.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/pack.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/pack.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/pack.cpython-38.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/tea.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/tea.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/tea.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/tea.cpython-38.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/tlv.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/tlv.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/tools.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/tools.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/tools.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/tools.cpython-38.pyc -------------------------------------------------------------------------------- /pymirai/utils/__pycache__/unpack.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synodriver/pymirai/2f455db584da342ebd1a113e78deb89b43f5df22/pymirai/utils/__pycache__/unpack.cpython-37.pyc -------------------------------------------------------------------------------- /pymirai/utils/pack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | python的二进制操作太挫了 需要加以改进 4 | """ 5 | 6 | 7 | class Pack(object): 8 | """ 9 | 组包 10 | """ 11 | 12 | def __init__(self): 13 | self.buffer = bytes() 14 | 15 | def set_empty(self) -> None: 16 | """ 17 | 清空 18 | :return: 19 | """ 20 | self.buffer = bytes() 21 | 22 | def get_all(self) -> bytes: 23 | """ 24 | 返回全部流 25 | :return: 26 | """ 27 | return self.buffer 28 | 29 | def write_hex(self, hexstr: str) -> None: 30 | str_bytes: str = hexstr.strip() 31 | pkt = bytes.fromhex(str_bytes) 32 | self.buffer += pkt 33 | 34 | def write_int(self, num: int) -> None: 35 | pkt = num.to_bytes(length=4, byteorder='big') 36 | self.buffer += pkt 37 | 38 | def write_short(self, num) -> None: 39 | pkt = int(num).to_bytes(length=2, byteorder='big') # , signed=True) 40 | self.buffer += pkt 41 | 42 | def write_str(self, text: str, encoding:str="utf-8") -> None: 43 | pkt = text.encode(encoding) 44 | self.buffer += pkt 45 | 46 | def write_bytes(self, byte: bytes) -> None: 47 | self.buffer += byte 48 | 49 | def write_qq(self, qq: int) -> None: 50 | """ 51 | 编码qq 12345 -> 31 32 33 34 35 52 | :param qq: 53 | :return: 54 | """ 55 | _qq = map(int, str(qq)) 56 | pkt: bytes = b"".join(bytes([48 | i]) for i in _qq) 57 | self.buffer += pkt 58 | 59 | def set_long_token(self, bin_) -> None: 60 | self.write_int(len(bin_)) 61 | self.write_bytes(bin_) 62 | 63 | def set_token(self, bin_) -> None: 64 | self.write_short(len(bin_)) 65 | self.write_bytes(bin_) 66 | -------------------------------------------------------------------------------- /pymirai/utils/tea.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import struct 3 | 4 | 5 | def xor(a, b): 6 | op = 0xffffffff 7 | a1, a2 = struct.unpack(b'>LL', a[0:8]) 8 | b1, b2 = struct.unpack(b'>LL', b[0:8]) 9 | return struct.pack(b'>LL', (a1 ^ b1) & op, (a2 ^ b2) & op) 10 | 11 | 12 | def tea_code(v, k): 13 | n = 16 14 | op = 0xffffffff 15 | delta = 0x9e3779b9 16 | k = struct.unpack(b'>LLLL', k[0:16]) 17 | y, z = struct.unpack(b'>LL', v[0:8]) 18 | s = 0 19 | for i in range(n): 20 | s += delta 21 | y += (op & (z << 4)) + k[0] ^ z + s ^ (op & (z >> 5)) + k[1] 22 | y &= op 23 | z += (op & (y << 4)) + k[2] ^ y + s ^ (op & (y >> 5)) + k[3] 24 | z &= op 25 | r = struct.pack(b'>LL', y, z) 26 | return r 27 | 28 | 29 | def tea_decipher(v, k): 30 | n = 16 31 | op = 0xffffffff 32 | y, z = struct.unpack(b'>LL', v[0:8]) 33 | a, b, c, d = struct.unpack(b'>LLLL', k[0:16]) 34 | delta = 0x9E3779B9 35 | s = (delta << 4) & op 36 | for i in range(n): 37 | z -= ((y << 4) + c) ^ (y + s) ^ ((y >> 5) + d) 38 | z &= op 39 | y -= ((z << 4) + a) ^ (z + s) ^ ((z >> 5) + b) 40 | y &= op 41 | s -= delta 42 | s &= op 43 | return struct.pack(b'>LL', y, z) 44 | 45 | 46 | class Tea: 47 | """QQ TEA 加解密, 64比特明码, 128比特密钥 48 | 这是一个确认线程安全的独立加密模块,使用时必须要有一个全局变量secret_key,要求大于等于16位 49 | """ 50 | 51 | @staticmethod 52 | def encrypt(v: bytes, secret_key: bytes): 53 | END_CHAR = b'\0' 54 | FILL_N_OR = 0xF8 55 | vl = len(v) 56 | filln = (8 - (vl + 2)) % 8 + 2 57 | fills = b'' 58 | for i in range(filln): 59 | fills = fills + bytes([220]) 60 | v = (bytes([(filln - 2) | FILL_N_OR]) 61 | + fills 62 | + v 63 | + END_CHAR * 7) 64 | tr = b'\0' * 8 65 | to = b'\0' * 8 66 | r = b'' 67 | o = b'\0' * 8 68 | for i in range(0, len(v), 8): 69 | o = xor(v[i:i + 8], tr) 70 | tr = xor(tea_code(o, secret_key), to) 71 | to = o 72 | r += tr 73 | return r 74 | 75 | @staticmethod 76 | def decrypt(v: bytes, secret_key: bytes): 77 | l = len(v) 78 | prePlain = tea_decipher(v, secret_key) 79 | pos = (prePlain[0] & 0x07) + 2 80 | r = prePlain 81 | preCrypt = v[0:8] 82 | for i in range(8, l, 8): 83 | x = xor(tea_decipher(xor(v[i:i + 8], prePlain), secret_key), preCrypt) 84 | prePlain = xor(x, preCrypt) 85 | preCrypt = v[i:i + 8] 86 | r += x 87 | if r[-7:] != b'\0' * 7: 88 | return None 89 | return r[pos + 1:-7] 90 | 91 | # if __name__ == '__main__': 92 | # global secret_key 93 | # secret_key = hex2bytes('11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11') 94 | # print(bytes2hex(secret_key)) 95 | 96 | # QQ = TEA() 97 | 98 | # plaintext = '哈哈哈hhh,' 99 | # plaintext = bytes(plaintext,encoding = "utf-8") 100 | # enc = QQ.encrypt(plaintext) 101 | # print(bytes2hex(enc)) 102 | # dec = QQ.decrypt(enc) 103 | # print(bytes2hex(dec)) 104 | -------------------------------------------------------------------------------- /pymirai/utils/tlv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .pack import Pack 3 | from .tools import getRandomBin,str2bytes 4 | from .tea import Tea 5 | 6 | class Tlv(object): 7 | """ 8 | 工具类 9 | """ 10 | def __init__(self): 11 | pass 12 | 13 | @staticmethod 14 | def tlv_pack(cmd, bin_): 15 | pack = Pack() 16 | pack.write_hex(cmd) 17 | pack.write_short(len(bin_)) 18 | pack.write_bytes(bin_) 19 | return pack.get_all() 20 | 21 | @staticmethod 22 | def tlv018(qq: str): 23 | pack = Pack() 24 | pack.write_hex('00 01 00 00 06 00 00 00 00 10 00 00 00 00') 25 | pack.write_int(int(qq)) 26 | pack.write_bytes(bytes(4)) 27 | return Tlv.tlv_pack('00 18', pack.get_all()) 28 | 29 | @staticmethod 30 | def tlv001(qq,time): 31 | pack = Pack() 32 | pack.write_hex('00 01') 33 | pack.write_bytes(getRandomBin(4)) 34 | pack.write_int(int(qq)) 35 | pack.write_int(time) 36 | pack.write_bytes(bytes(6)) 37 | return Tlv.tlv_pack('00 01',pack.get_all()) 38 | 39 | @staticmethod 40 | def tlv106(qq:str,md5pass,md52pass,tgtkey,deviceguid:str,time,appid): 41 | pack = Pack() 42 | pack.write_hex('00 04') 43 | pack.write_bytes(getRandomBin(4)) 44 | pack.write_hex('00 00 00 0D') 45 | pack.write_hex('00 00 00 10') 46 | pack.write_hex('00 00 00 00') 47 | pack.write_hex('00 00 00 00') 48 | pack.write_int(int(qq)) 49 | pack.write_int(time) 50 | pack.write_hex('00 00 00 00 01') 51 | pack.write_bytes(md5pass) 52 | pack.write_bytes(tgtkey) 53 | pack.write_hex('00 00 00 00 01') 54 | pack.write_hex(deviceguid) 55 | pack.write_int(appid) 56 | pack.write_hex('00 00 00 01') 57 | pack.write_short(len(qq)) 58 | pack.write_str(qq) 59 | pack.write_hex('00 00') 60 | return Tlv.tlv_pack('01 06',Tea.encrypt(pack.get_all(),md52pass)) 61 | 62 | @staticmethod 63 | def tlv116(mtype): 64 | pack = Pack() 65 | if mtype == 1: 66 | pack.write_hex('00 08 F7 FF 7C 00 01 04 00 01 5F 5E 10 E2') 67 | elif mtype == 2: 68 | pack.write_hex('00 08 F7 FF 7C 00 01 04 00 01') 69 | else: 70 | pack.write_hex('00 0A F7 FF 7C 00 01 04 00 01 5F 5E 10 E2') 71 | return Tlv.tlv_pack('01 16',pack.get_all()) 72 | 73 | @staticmethod 74 | def tlv100(appid,main_signmap): 75 | pack = Pack() 76 | pack.write_hex('00 01 00 00 00 0D 00 00 00 10') 77 | pack.write_int(appid) 78 | pack.write_bytes(bytes(4)) 79 | pack.write_int(main_signmap) 80 | return Tlv.tlv_pack('01 00',pack.get_all()) 81 | 82 | @staticmethod 83 | def tlv107(): 84 | pack = Pack() 85 | pack.write_hex('00 00 00 00 00 01') 86 | return Tlv.tlv_pack('01 07',pack.get_all()) 87 | 88 | @staticmethod 89 | def tlv142(apkid:str): 90 | pack = Pack() 91 | pack.write_int(len(apkid)) 92 | pack.write_str(apkid) 93 | return Tlv.tlv_pack('01 42',pack.get_all()) 94 | 95 | @staticmethod 96 | def tlv109(AndroidId): 97 | pack = Pack() 98 | pack.write_hex(AndroidId) 99 | return Tlv.tlv_pack('01 09',pack.get_all()) 100 | 101 | @staticmethod 102 | def tlv124(): 103 | pack = Pack() 104 | pack.write_hex('00 07') 105 | pack.write_hex('61 6E 64 72 6F 69 64') #android 106 | pack.write_hex('00 05') 107 | pack.write_hex('35 2E 31 2E 31') #5.1.1 108 | pack.write_hex('00 02') 109 | pack.write_hex('00 10') 110 | pack.write_hex('43 68 69 6E 61 20 4D 6F 62 69 6C 65 20 47 53 4D') #China Mobile GSM 111 | pack.write_hex('00 00 00 04') 112 | pack.write_hex('77 69 66 69') #wifi 113 | return Tlv.tlv_pack('01 24',pack.get_all()) 114 | 115 | @staticmethod 116 | def tlv128(devicename,devicebrand,deviceguid): 117 | pack = Pack() 118 | pack.write_hex('00 00 01 01 00 11 00 00 00') 119 | pack.write_short(len(devicename)) 120 | pack.write_str(devicename) 121 | pack.write_hex('00 10') 122 | pack.write_hex(deviceguid) 123 | pack.write_short(len(devicebrand)) 124 | pack.write_str(devicebrand) 125 | return Tlv.tlv_pack('01 28',pack.get_all()) 126 | 127 | @staticmethod 128 | def tlv16E(devicename): 129 | pack = Pack() 130 | pack.write_str(devicename) 131 | return Tlv.tlv_pack('01 6E',pack.get_all()) 132 | 133 | @staticmethod 134 | def tlv144(tgtkey,tlv109,tlv124,tlv128,tlv16E): 135 | pack = Pack() 136 | pack.write_short(4) 137 | pack.write_bytes(tlv109) 138 | pack.write_bytes(tlv124) 139 | pack.write_bytes(tlv128) 140 | pack.write_bytes(tlv16E) 141 | return Tlv.tlv_pack('01 44',Tea.encrypt(pack.get_all(),tgtkey)) 142 | 143 | @staticmethod 144 | def tlv145(deviceguid): 145 | pack = Pack() 146 | pack.write_hex(deviceguid) 147 | return Tlv.tlv_pack('01 45',pack.get_all()) 148 | 149 | @staticmethod 150 | def tlv147(apk_v:str,apk_sig:str): 151 | pack = Pack() 152 | pack.write_hex('00 00 00 10') 153 | pack.write_short(len(apk_v)) 154 | pack.write_str(apk_v) 155 | pack.write_hex('00 10') 156 | pack.write_hex(apk_sig) 157 | return Tlv.tlv_pack('01 47',pack.get_all()) 158 | 159 | @staticmethod 160 | def tlv154(reqid): 161 | pack = Pack() 162 | pack.write_int(reqid) 163 | return Tlv.tlv_pack('01 54',pack.get_all()) 164 | 165 | @staticmethod 166 | def tlv141(): 167 | pack = Pack() 168 | pack.write_hex('00 01') 169 | pack.write_hex('00 10') 170 | pack.write_hex('43 68 69 6E 61 20 4D 6F 62 69 6C 65 20 47 53 4D') 171 | pack.write_hex('00 02') 172 | pack.write_hex('00 04') 173 | pack.write_hex('77 69 66 69') 174 | return Tlv.tlv_pack('01 41',pack.get_all()) 175 | 176 | @staticmethod 177 | def tlv008(): 178 | pack = Pack() 179 | pack.write_hex('00 00 00 00 08 04 00 00') 180 | return Tlv.tlv_pack('00 08',pack.get_all()) 181 | 182 | @staticmethod 183 | def tlv511(): 184 | pack = Pack() 185 | domainlist = ['tenpay.com','qzone.qq.com','vip.qq.com','qun.qq.com','yundong.qq.com'] 186 | pack.write_short(len(domainlist)) 187 | for x in domainlist: 188 | pack.write_hex('01') 189 | pack.set_token(str2bytes(x)) 190 | return Tlv.tlv_pack('05 11',pack.get_all()) 191 | 192 | @staticmethod 193 | def tlv187(devicemac:str): 194 | pack = Pack() 195 | pack.write_hex(devicemac) 196 | return Tlv.tlv_pack('01 87',pack.get_all()) 197 | 198 | @staticmethod 199 | def tlv188(AndroidId:str): 200 | pack = Pack() 201 | pack.write_hex(AndroidId) 202 | return Tlv.tlv_pack('01 88',pack.get_all()) 203 | 204 | @staticmethod 205 | def tlv194(imsi:str): 206 | pack = Pack() 207 | # pack.write_hex(imsi) 208 | pack.write_hex('DE 99 6F 72 08 45 79 04 DE B5 AF 92 27 8E 40 A2') 209 | return Tlv.tlv_pack('01 94',pack.get_all()) 210 | 211 | @staticmethod 212 | def tlv191(): 213 | pack = Pack() 214 | pack.write_hex('82') #02 215 | return Tlv.tlv_pack('01 91',pack.get_all()) 216 | 217 | @staticmethod 218 | def tlv202(bssid,ssid): 219 | pack = Pack() 220 | pack.write_hex('00 10') 221 | pack.write_hex('41 D8 57 AF BD 54 DC 49 DB 42 14 44 7D 09 5D 13') 222 | pack.set_token(str2bytes('"' + ssid + '"')) 223 | return Tlv.tlv_pack('02 02',pack.get_all()) 224 | 225 | @staticmethod 226 | def tlv177(time,sdkversion): 227 | pack = Pack() 228 | pack.write_hex('01') 229 | pack.write_int(time) 230 | pack.write_short(len(sdkversion)) 231 | pack.write_str(sdkversion) 232 | return Tlv.tlv_pack('01 77',pack.get_all()) 233 | 234 | @staticmethod 235 | def tlv516(): 236 | pack = Pack() 237 | pack.write_bytes(bytes(4)) 238 | return Tlv.tlv_pack('05 16',pack.get_all()) 239 | 240 | @staticmethod 241 | def tlv521(): 242 | pack = Pack() 243 | pack.write_bytes(bytes(6)) 244 | return Tlv.tlv_pack('05 21',pack.get_all()) 245 | 246 | @staticmethod 247 | def tlv525(): 248 | pack = Pack() 249 | pack.write_hex('00 01 05 36 00 02 01 00') 250 | return Tlv.tlv_pack('05 25',pack.get_all()) -------------------------------------------------------------------------------- /pymirai/utils/tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 操作字节流的 4 | """ 5 | import struct 6 | import random 7 | 8 | def int2bytes(num: int, outlen: int) -> bytes: 9 | return num.to_bytes(length=outlen, byteorder='big') 10 | 11 | def bytes2int(bin_) -> int: 12 | return int.from_bytes(bin_,byteorder='big') 13 | 14 | def hex2bytes(hexstr: str) -> bytes: 15 | str_bytes = hexstr.strip().replace("\n", "") 16 | pkt = bytes.fromhex(str_bytes) 17 | return pkt 18 | 19 | 20 | def bytes2hex(bin_: bytes) -> str: 21 | return ''.join(['%02X ' % b for b in bin_]) 22 | 23 | 24 | def _bytes2hex(bin_: bytes) -> str: 25 | return bin_.hex().upper() 26 | 27 | 28 | def str2bytes(text: str): 29 | return text.encode('utf-8') 30 | 31 | 32 | def str2hex(text: str): 33 | strBytes = text.encode('utf-8') 34 | return bytes2hex(strBytes) 35 | 36 | 37 | def hex2str(hexstr: str): 38 | strBytes = hexstr.split() 39 | pkt = bytearray(int(x, 16) for x in strBytes) 40 | return pkt.decode('utf-8') 41 | 42 | def getRandomBin(num): 43 | intlist = [random.randint(0,255) for i in range(num)] 44 | pkt = bytearray(intlist) 45 | return pkt -------------------------------------------------------------------------------- /pymirai/utils/unpack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | class Unpack(object): 4 | def __init__(self,pkt=bytes()): 5 | self.pkt = pkt 6 | 7 | def setData(self,pkt): 8 | self.pkt = pkt 9 | 10 | def getBin(self,num): 11 | ret = self.pkt[:num] 12 | self.pkt = self.pkt[num:] 13 | return ret 14 | 15 | def getInt(self): 16 | ret = self.pkt[:4] 17 | self.pkt = self.pkt[4:] 18 | ret = int.from_bytes(ret,byteorder='big') 19 | return ret 20 | 21 | def getShort(self): 22 | ret = self.pkt[:2] 23 | self.pkt = self.pkt[2:] 24 | ret = int.from_bytes(ret,byteorder='big') 25 | return ret 26 | 27 | def getAll(self): 28 | ret = self.pkt 29 | self.pkt = bytes() 30 | return ret -------------------------------------------------------------------------------- /tests/data.txt: -------------------------------------------------------------------------------- 1 | FC 06 E7 00 2 | 8E 7E C9 8B 92 D6 D9 BB 01 76 6E 2A 2F 71 BA FE 3 | 63 D2 EE D0 AE F0 18 48 70 E5 7A D2 8F 92 1F E7 4 | 56 C4 96 A3 3C 01 9A 1F 70 85 C0 E6 D8 26 27 EE 5 | 7D 90 53 ED AC 8A 4B 83 83 CF 87 25 E9 08 2E 87 6 | 33 C9 45 10 2C B5 B5 2B 0F 57 0E E4 9F 8D 0B 35 7 | 32 3F 02 6F 17 2F 33 2D E0 B8 51 7E 25 04 EA 31 8 | 89 76 75 FE 24 1D AF 9A D0 48 36 7E 14 B8 B1 8B 9 | EE BA 1E 4B 73 5F EC 30 04 A1 4C 88 E2 F7 13 1C 10 | 33 F7 D5 02 08 16 40 85 32 AE 84 46 57 70 B9 01 11 | ED 0E 63 9C 13 71 7E 0C 56 59 DB F3 29 CE DA 7A 12 | D1 A1 53 D5 92 C8 51 40 BA 75 D6 84 A1 7E 75 3C 13 | 98 A7 4B 80 FF 8C 4E 4D 15 63 E8 68 33 17 33 D3 14 | 7D 8E 0A 99 06 9C 3E 51 9C 59 DB E4 45 49 D0 09 15 | C7 9D E2 F2 53 BE 9E 45 F8 3E 7C B4 9B FC 11 A1 16 | 7A 3D 66 7A 0A 23 8F FC ED F1 98 EC C3 37 8B 5C 17 | E6 8E 12 86 E6 AF F1 50 F1 11 3A 94 28 76 EC 7B 18 | 3A C3 44 73 31 B4 AD EA C8 88 08 8B 33 F3 D0 31 19 | 20 86 FD AA 82 B9 CB 7F ED 0D 87 34 4E 3A AC 57 20 | 7E 73 59 D2 AB CA 90 6A BE 80 4C 75 A4 AE FF 48 21 | 96 24 4B 30 21 33 86 A5 E0 A2 D4 35 EE 5D 0B D4 22 | 75 E9 50 85 2D 9A E3 AC FD 76 D1 77 0D 40 CD 17 23 | FC 75 AD 5D CD 1C 11 D6 EA 9E 8E 24 5D 3F 92 99 24 | BE 8D 6E D5 EE 0F 80 D7 98 33 38 63 28 C5 25 AB 25 | 73 DD 28 68 67 EF EB B3 0A F8 60 56 63 E2 D8 DB 26 | 5F 9C 93 76 FC ED 65 8C EE 04 6D F7 96 D5 5E 27 27 | B8 DC E0 8B 0A 8B A2 18 7A 02 36 CB 0B 99 A6 A4 28 | DB 99 6B 86 83 1C C2 39 A0 D0 0C EC FA E8 33 70 29 | 36 57 AF AD A1 88 F9 D6 A2 BE 88 73 79 05 E6 34 30 | 57 63 C4 59 04 98 75 00 15 13 88 47 FC 0A 4C 3E 31 | 28 54 EC 92 36 CB A5 A7 AB E8 02 B3 1A 16 F7 A7 32 | 6E 41 07 5B 4B FA 20 2C 95 B9 06 30 73 A6 8A 81 33 | 05 49 A0 6F B8 5E 22 CC 05 2C 23 CC FD 14 70 16 34 | 07 44 88 12 2B CF E5 79 EA E5 D8 EB 5F 63 31 88 35 | 28 15 38 DA 60 CD AB F2 EE 4A 4A E0 D5 3A 7C CA 36 | 45 77 EA D0 E4 C0 3A DF 41 E4 CF 23 54 6C D2 8C 37 | B6 07 34 EA 61 59 55 03 FC 65 D9 33 EA 44 56 49 38 | A7 2F 84 F8 19 6F 6C 60 09 04 C2 FB 42 ED 3A 93 39 | B5 84 E0 4D E6 EA 1C 25 F2 90 D0 08 0A 37 FC BE 40 | DD 07 28 C3 BE 54 7C 92 EE EC 3C B2 A9 B2 A6 2F 41 | 98 A5 03 36 94 19 44 24 20 0E DD 70 10 27 DC A9 42 | 0F A9 DF 52 F3 A8 0F 84 8B BC 35 1A E1 A6 61 06 43 | 99 F4 F3 1D 90 AF 24 50 2C 67 1C 72 54 DE 1E 54 44 | 96 57 CE EF 5C E8 6E 4C 0C E1 6B 57 DA 88 01 B4 45 | 3D A4 BE 36 04 48 D8 CB D4 89 EC EF B8 DE 89 D0 46 | E3 CB 97 61 FA E3 CA 48 6C FD 95 63 F7 38 DC 03 47 | AE 82 C6 2D BD B2 17 E4 CF FD C9 EE 84 87 F4 64 48 | 2A 51 EF 78 5C 0D 15 05 1B 9E 97 96 9C 74 15 34 49 | F6 95 E6 FF C5 35 9C 1B 0F D7 00 91 7D B1 FA 02 50 | 61 03 BF F1 46 CE EB 52 DF EC D8 BE 64 CD 78 43 51 | AC 54 1C DF FD 82 C2 86 4F 48 A0 02 1C 33 B9 CD 52 | 93 E0 D6 8D ED CC 6F 6D 04 39 59 1F B0 C7 71 DD 53 | 59 6F C0 30 0A 25 4A 43 B2 64 0A C3 0D AC 2B BA 54 | 25 40 E2 CF BA D9 C7 4C 5C 83 4B 80 B2 EF 34 9F 55 | E9 B1 D6 4A E0 7D 0F 5B 5E C2 6B 7F EE AF 4E 7C 56 | 5F 17 77 50 35 50 D3 FE 0C 23 07 01 CF 13 BC EA 57 | A8 58 DE 26 66 F5 E6 ED 85 48 91 CC 56 ED A1 98 58 | E7 3D 75 AA 33 4E 73 52 8F 24 1D 42 04 99 73 C0 59 | 82 AB 7E 67 BA 12 BC 9E 94 7C 3E 88 13 41 ED 95 60 | 3E 5E C3 16 10 B7 5B D3 ED 28 C1 0B E2 91 5C CC 61 | A9 8A BF 97 3D 72 44 ED 84 06 7A D5 21 34 7B AE 62 | 76 22 B7 CD 08 70 3D AE 97 4D C3 3E 23 73 77 27 63 | 53 EA 9C 29 14 6E E5 42 8E 56 32 94 2B B7 D0 86 64 | 0A 39 1A B1 AA 22 67 F3 88 C7 78 40 1D 37 1E 67 65 | ED E7 AF 63 EA E8 2B 17 56 93 18 DD 8B 49 88 62 66 | F4 EA C4 78 3F F2 10 4B 9E 63 12 A5 CE 01 53 22 67 | 5A B1 C7 DF 95 B0 75 51 5D 27 F7 29 12 AC E7 70 68 | 3B 49 B8 40 44 BE 5E 05 3A 23 EC 3A 3E 41 D5 23 69 | 29 A1 39 EE 39 15 F1 AD 19 22 A0 75 33 83 1F 21 70 | 12 9A D6 CC B9 D0 70 89 40 94 81 D6 26 86 8B 38 71 | 6A A0 AE 45 07 A3 80 B5 82 D6 1B 52 61 D3 52 B6 72 | 32 A5 A5 8C 40 07 A9 02 63 B2 1D FA A8 7C AB 7A 73 | 5B 64 F2 3F 88 7C E0 92 CD B4 1E 50 C1 A1 80 07 74 | 39 05 48 E7 8C 8F 5D 6A F8 CD D8 66 0F 48 4F 42 75 | BA 70 59 2B A4 A2 62 51 57 95 9B 40 D7 D9 0D D4 76 | 81 68 28 82 31 F2 3C 1D 63 A4 4C 2B A2 45 B1 C7 77 | 7A 85 59 90 4A E7 65 3A 6D C9 D4 BF 34 BF 5C 28 78 | 5F B0 B5 95 C7 5C C0 BA F6 1E 85 B0 61 C6 A7 96 79 | CC 16 14 AA 55 E7 5C 8B AF 16 0E 2B 8E 18 B2 FD 80 | 5B 86 F9 59 09 CC 90 C2 02 78 A5 85 9F 17 26 75 81 | 24 66 11 D8 0E BF 3A E5 0B 3C CB 85 86 EC 42 64 82 | C7 B2 48 AB 13 68 2B 59 71 16 F0 BB E2 91 C4 95 83 | DD 07 F4 A1 C2 77 44 F5 FC 66 C2 31 6D F8 1F 42 84 | 40 38 05 24 2D 2B 47 F9 AC E3 E7 75 A8 D7 56 16 85 | 3C E5 AE 54 F9 85 C8 87 0C 07 E6 30 8A 9D 1F 22 86 | 6A DA 1F EA FA 2D ED 10 F6 9F 6F 24 C6 B3 99 A8 87 | B3 28 21 60 CE C2 68 68 5B AB F0 66 6F CD AC 8D 88 | A9 AB D3 CC 2E 10 D3 E1 CF 28 01 9D 05 4D 2F 63 89 | 2D 82 0A F2 C1 7C AF 11 80 50 90 64 47 7B 8E 03 90 | C2 70 C2 CB 8B FD 29 8B 8C D8 DF 71 B9 DC A0 AF 91 | FB 75 09 F5 41 40 AC 40 66 71 9E 84 95 12 C1 05 92 | DE D3 63 66 CE F0 29 6B 48 2C D7 9A 72 D8 46 F1 93 | 1C 87 AE C1 DD EF B2 25 EA 56 91 6C 77 F6 35 D6 94 | 73 1F 27 1F CD 41 7B 5E 84 98 D5 D7 ED B9 EF 42 95 | 46 F3 59 15 11 4E EB D4 A5 17 8A F3 AE EB 83 AA 96 | 92 6B 26 42 4C 19 56 72 C8 40 67 2F F0 C2 D9 CC 97 | D6 B6 AC C9 86 E4 CA A0 9B 94 EB 2E 2D 7E 98 3C 98 | 7F A4 5E A9 6F 1C 22 4C 97 09 D6 24 8F DF 27 48 99 | F2 FD DE BC 57 F0 9C C7 E2 4B EB 9F 53 A9 C2 3A 100 | 3D F1 7E 5D B7 79 3C EB D1 27 54 4B 7F 17 F9 DC 101 | 1B FB 3C 69 1A 1A 53 84 07 45 12 5F 4B 61 CE 55 102 | FF AC B1 AC 4C 40 6B 48 47 35 BB 78 B0 45 25 6D 103 | F9 56 FF 5E 54 04 83 E5 E2 E0 F9 59 81 A1 BE A6 104 | EB CC 5E E1 BB 35 AD 86 DB A5 7A 44 38 91 81 F1 105 | B1 14 30 67 95 39 36 AB BF 32 E9 DF F8 87 EA A1 106 | 2B E1 2D 01 CE A8 41 4D 0D 8B 67 18 D8 43 FD 41 107 | CB 38 2E 1C -------------------------------------------------------------------------------- /tests/test_login.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import asyncio 3 | 4 | from pymirai.utils.pack import Pack 5 | 6 | 7 | async def login(qq: int, password: str): 8 | reader, writer = await asyncio.open_connection("113.96.12.224", 8080) 9 | pack = Pack() 10 | pack.write_hex("00 00 06 B4 00 00 00 0A 02 00 00 00 04 00 00 00 00 0E") 11 | pack.write_qq(qq) 12 | writer.write(pack.get_all()) 13 | await writer.drain() -------------------------------------------------------------------------------- /tests/test_pack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | from unittest import TestCase 4 | 5 | from pymirai.utils.tools import * 6 | from pymirai.utils.pack import Pack 7 | from pymirai.utils.tea import Tea 8 | 9 | 10 | class TestPack(TestCase): 11 | 12 | def test_pack(self): 13 | a = bytes2hex(Tea.encrypt(str2bytes('哈哈哈'), hex2bytes('12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12'))) 14 | # print(a) 15 | b = bytes2hex(Tea.decrypt(hex2bytes(a), hex2bytes('12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12'))) 16 | self.assertEqual(hex2str(b), "哈哈哈") 17 | p = Pack() 18 | p.write_qq(123456) 19 | self.assertEqual(p.get_all(), hex2bytes("31 32 33 34 35 36")) 20 | 21 | def test_decode(self): 22 | with open("data.txt") as f: 23 | a = f.read().strip() 24 | data = Tea.decrypt((hex2bytes(a)), bytes(16)) 25 | with open("login", "wb") as f2: 26 | f2.write(data) 27 | pass 28 | 29 | 30 | if __name__ == "__main__": 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /tests/test_signal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | import asyncio 4 | from unittest import IsolatedAsyncioTestCase 5 | 6 | from pymirai.signals import SignalManager 7 | 8 | 9 | class TestPack(IsolatedAsyncioTestCase): 10 | async def a(self, t): 11 | await asyncio.sleep(t) 12 | print(f"收到了a信号 {t}") 13 | return t 14 | 15 | async def b(self, t): 16 | await asyncio.sleep(t) 17 | print(f"收到了b信号 {t}") 18 | return t 19 | 20 | async def asyncSetUp(self) -> None: 21 | self.signal = SignalManager() 22 | self.signal.connect(self.a, "a") 23 | self.signal.connect(self.b, "b") 24 | 25 | async def test_a(self): 26 | data = await self.signal.send_async("a", 1) 27 | self.assertEqual(len(data), 1, "接收到信号的函数个数不正确") 28 | self.assertEqual(data[0][0], self.a, "错误的函数接收到了信号") 29 | self.assertEqual(data[0][1], 1, "返回值错误") 30 | 31 | async def test_b(self): 32 | data = await self.signal.send_async("b", 1) 33 | self.assertEqual(len(data), 1, "接收到信号的函数个数不正确") 34 | self.assertEqual(data[0][0], self.b, "错误的函数接收到了信号") 35 | self.assertEqual(data[0][1], 1, "返回值错误") 36 | 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | --------------------------------------------------------------------------------