├── README.md ├── apiServer.py ├── config ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── enumExec.cpython-38.pyc │ ├── logger.cpython-38.pyc │ └── manager.cpython-38.pyc ├── base │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── config.cpython-38.pyc │ │ ├── serialize.cpython-38.pyc │ │ └── transform.cpython-38.pyc │ ├── config.py │ ├── serialize.py │ └── transform.py ├── config.py ├── enumExec.py ├── logger.py ├── manager.py ├── transforms │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── config_dict.cpython-38.pyc │ │ ├── dict_json.cpython-38.pyc │ │ ├── dict_keyval.cpython-38.pyc │ │ ├── filter.cpython-38.pyc │ │ ├── map.cpython-38.pyc │ │ ├── meta.cpython-38.pyc │ │ └── props.cpython-38.pyc │ ├── config_dict.py │ ├── dict_json.py │ ├── dict_keyval.py │ ├── filter.py │ ├── map.py │ ├── meta.py │ ├── props.py │ └── serialize.py └── v1 │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── config.cpython-38.pyc │ └── serialize.cpython-38.pyc │ ├── config.py │ └── serialize.py ├── env ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── env.cpython-38.pyc │ ├── env_app.cpython-38.pyc │ ├── env_config.cpython-38.pyc │ ├── env_config_id.cpython-38.pyc │ ├── env_config_kh.cpython-38.pyc │ ├── env_config_kz.cpython-38.pyc │ ├── env_config_ph.cpython-38.pyc │ ├── env_config_ru.cpython-38.pyc │ └── env_ios.cpython-38.pyc ├── env.py ├── env_android.py ├── env_app.py ├── env_config.py ├── env_config_hk.py ├── env_config_id.py ├── env_config_kh.py ├── env_config_kz.py ├── env_config_ph.py ├── env_config_ru.py ├── env_config_us.py └── env_config_vn.py ├── examples ├── walogin_handshake_ik.py ├── walogin_handshake_xx.py └── walogin_handshake_xxfallback.py ├── login.png ├── messagestack ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── group_info.cpython-38.pyc │ ├── play_group.cpython-38.pyc │ └── register.cpython-38.pyc ├── group_info.py ├── group_msg_catch.py ├── play_group.py └── register.py ├── registration ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── coderequest.cpython-38.pyc │ ├── existsrequest.cpython-38.pyc │ └── regrequest.cpython-38.pyc ├── clientLogRequest.py ├── coderequest.py ├── existsrequest.py └── regrequest.py ├── resource └── WA_Security_WhitePaper.pdf └── versions └── whatsapp_2.21.110.zip /README.md: -------------------------------------------------------------------------------- 1 | # whatsapp api Share about yowsup telegram:@tgforme2 2 | 3 | **基于WhatsApp iOS协议逆向的共享资源** 4 | 5 | ** WhatsApp api 全量协议(可实现注册登录,发消息,群组, 炒群, 拉群,头像功能,有需要技术支持及源码可联系 telegram:@tgforme2) ** 6 | 7 | ** WhatsApp api Full protocol (can use registration and login, sending messages, group, avatar functions, technical support and source code can be contacted telegram:@tgforme2) ** 8 | 9 | **** 10 | !!!WhatsApp ws 新号 协议号 拉群号 美国出售 量大价低 11 | 12 | **** 13 | ## 0x01. WhatsApp ipa 脱壳 历史版本 14 | 15 | 16 | - 最近版本: [whatsapp_2.21.110.zip](/versions/) 17 | 18 | 19 | 20 | **** 21 | ## 0x02. WhatsApp iOS协议部分 22 | ![](/login.png) 23 | 24 | 25 | # **0x01 工具** 26 | 27 | 首先是要用到的工具,中间主要用了ida,hopper和lldb 28 | 29 | - dumpdecrypted: 将苹果加密过的app砸壳 30 | - class-dump: 导出MachO文件里ObjC类及方法定义 31 | - CydiaSubstrate: 将第三方动态库注入进程 32 | - Cycript: 用js语法写ObjC方法 33 | - Theos: 越狱插件开发工具 34 | - IDA: 反汇编、反编译工具 35 | - Hopper: OSX反汇编、反编译工具 36 | - Debugserver + LLDB: 动态调试器 37 | 38 | # **0x02 ARM指令** 39 | 40 | ``` 41 | arm是RISC结构,数据从内存到CPU之间移动只能通过L/S指令来完成,就是ldr/str指令 42 | ldr 把数据从内存移到cpu 43 | str 把cpu的数据数据转移到内存 44 | lldb读取内存的数据,memory read 45 | ldr r0, 0x12345678 //把0x12345678这个地址中的值存放到r0中 46 | ldr r0, =0x12345678 //把0x12345678这个地址写到r0中 47 | 例子: 48 | COUNT EQU 0x40003100 //定义一个COUNT变量,地址是0x40003100 49 | ... 50 | LDR R1,=COUNT //将COUNT这个变量的地址,也就是0x40003100放到R1中 51 | MOV R0,#0 //将立即数0放到R0中 52 | STR R0,[R1] //将R0中的值放到以R1中的值为地址的存储单元去 53 | 54 | B 跳转指令 55 | BL 带返回的跳转指令 56 | BLX 带返回和状态切换的跳转指令 57 | BX 带状态切换的跳转指令 58 | 59 | BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态从ARM切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14,因此,当子程序使用Thumb指令集,而调用者者使用ARM指令集,可以通过BLX指令实现子程序的调用和处理器工作状态切换,同时,子程序的返回可以通过将寄存器R14的值复制到PC中来完成。 60 |  R0-R3:        用于函数参数及返回值的传递,超过4个参数,其它参数存在栈中,在ARM中栈是向下生长的,R0还可以作为返回值。 61 |   R4-R6, R8, R10-R11: 没有特殊规定,就是普通的通用寄存器 62 |   R7:          栈帧指针,指向母函数与被调用子函数在栈中的交界。 63 |   R9:          在iOS3.0被操作系统保留 64 |   R12:          内部过程调用寄存器,动态链接时会用到,不必深究 65 |   R13:          SP(stack pointer),是栈顶指针 66 |   R14:          LR(link register),存放函数的返回地址。 67 |   R15:          PC(program counter),指向当前指令地址。 68 | ADC    带进位的加法 69 |   ADD    加法 70 |   AND    逻辑与 71 |   B     分支跳转,很少单独使用 72 |   BL 分支跳转,跳转后返回地址存入r14 73 |   BX 分支跳转,并切换指令模式(Thumb/ARM) 74 |   CMP 比较值,结果存在程序状态寄存器,一般用于分支判断 75 |   BEQ 结果为0则跳转 76 |   BNE 结果不为0跳转 77 |   LDR 加载寄存器,从内存加载到寄存器 78 |   LDRB 装载字节到寄存器 79 |   LDRH 装载半字到寄存器(一个字是32位) 80 |   LSL 逻辑左移 这是一个选项,不是指令 81 |   LSR 逻辑右移 这是一个选项,不是指令 82 |   MOV 传送值/寄存器到一个寄存器 83 |   STR 存储一个寄存器,寄存器值存到内存 84 |   STRB 存储一个字节 85 |   STRH 存储一个半字 86 |   SUB 减法 87 |   PUSH POP 堆栈操作 88 | 89 | 有时候需要 90 | 91 | db ;定义字节类型变量,一个字节数据占一个字节单元,读完一个偏移量加1 92 | dw ;定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2 93 | dd ;定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4 94 | 95 | IDA给某个位置命名的时,它会使用该位置的虚拟地址和表示一个该地址的类型的前缀进行命名: 96 | sub_xxx ;地址xxx处的子例程 97 | loc_xxx ;地址xxx处的一个指令 98 | byte_xxx ;位置xxx处的8位数据 99 | word_xxx ;位置xxx处的16位数据 100 | dword_xxx ;位置xxx处的32位数据 101 | unk_xxx ;位置xxx处大小未知的数据 102 | ``` 103 | 104 | ``` 105 | 关于sp,bp等栈寄存器的解释: 106 | SP is stack pointer. The stack is generally used to hold "automatic" variables and context/parameters across function calls. Conceptually you can think of the "stack" as a place where you "pile" your data. You keep "stacking" one piece of data over the other and the stack pointer tells you how "high" your "stack" of data is. You can remove data from the "top" of the "stack" and make it shorter. 107 | 108 | 109 | 110 | 111 | 虽然是英文,但是看起来要比中文易懂 112 | ``` 113 | 114 | ida里面有三种颜色的箭头: 115 | 116 | 1. 蓝色,顺序执行 117 | 2. 绿色,条件为(YES) 118 | 3. 红色,条件为(NO) 119 | 120 | # **0x03 lldb使用方法** 121 | 122 | ``` 123 | lldb操作相关指令 124 | image list -o -f 查看进程在虚拟内存中相对模块基地址 125 | br s -a [addr] 打断点 126 | breakpoint delete 删除断点 127 | s/n 是针对源代码 128 | br list 列出所有断点 129 | br dis 1 禁用序号为1的断点 130 | jump
跳转到新地址 131 | 132 | ni 断点的单步之行, netxi(next instruction简写:ni) 133 | si stepi(step instruction 简写:si) 134 | display /10i $pc-16 显示当前PC附近的10条指令 135 | 136 | si会进入函数之行,ni执行完但是不会进入函数内,执行过程中可以利用display /i $pc来看下一个执行的instruction是什么 137 | 138 | c 放开执行该断点 139 | p 输出某个寄存器的值 140 | p $r0 输出寄存器的内容 141 | 也可以将一个地址所存放的值进行打印 142 | p/x $sp 就是输出$sp指针所指的地址处存放的值,以16进制表示 143 | po (char *)$r2 po打印Object-C对象 144 | register read --all 读取所有的寄存器内容 145 | thread list //打印所有线程 146 | thread select //跳到某个线程 147 | thread info //输出当前线程信息 148 | frame variable //打印当前栈所有变量 149 | frame variable '变量名' //打印某个变量 150 | frame info 查看当前帧栈信息 151 | frame select 跳到指定帧栈 152 | 153 | ``` 154 | 155 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fab55b3e-f5fc-47fc-852e-3b594f06239d/Untitled.png) 156 | 157 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bf7ab224-5abd-47de-aece-a5affc5e2887/Untitled.png) 158 | 159 | frida的常见用法: 160 | 161 | - hook函数(IOS中theos具备的功能) 162 | - 记录函数执行日至(IOS中theos具备的功能) 163 | - 调用函数(IOS中cycript具备的功能) 164 | - 读写内存(类似调试器的功能) 165 | 166 | lldb: 167 | 168 | - lldb在object-c类对象所有函数设置断点: `breakpoint set -r '\[ClassName .*\]$'` 169 | 170 | 常用: 171 | 172 | ``` 173 | breakpoint set --name 174 | "set a breakpoint on a given function name, globally. eg. 175 | breakpoint set --name viewDidLoad 176 | or 177 | breakpoint set --name "-[UIView setFrame:]" 178 | 179 | break set --selector 180 | "set a breakpoint on a selector, globally. e.g., 181 | breakpoint set --selector dealloc 182 | bt //查看堆栈 183 | frame select 184 | thread list 185 | expression $r6 //查看r6寄存器的值 186 | 1. 加参数可以更改显示方式,如/x十六进制打印 187 | 2. po一般用作查看对象信息 188 | 3. po的命令是“expression -O -"命令的别名 189 | 190 | 第一次使用malloc_info需要在lld里面导入lldb.macosx.heap 191 | malloc_info -s
192 | memory read 读取内存的值 193 | ``` 194 | 195 | ### **0x04 Hopper基本使用** 196 | 197 | hopper和LLDB所选择的ARM架构位数得一致,要么是32位,要么都是64位,计算公式:hopper里面显示的都是"模块偏移前基地址",而lldb要操作的都是"模块偏移后的基地址",所以从hopper到lldb要做一个地址偏移量的转换。 198 | 199 | ``` 200 | 偏移后模块基地址 = 偏移前模块地址 + ALSR 201 | ``` 202 | 203 | 偏移前地址从Hopper看: 204 | 205 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/45ced21a-8ae8-4ed8-a6df-cd7aae57a123/Untitled.png) 206 | 207 | ALSR偏移地址从LLDB看: 208 | 209 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/3a3d8caf-af54-462f-ba93-d620d162ec01/Untitled.png) 210 | 211 | 由上图可知ASLR偏移:30000偏移后基地址为:34000 212 | 213 | (从hopper的login搜索找到方法[WCAccountPhoneLoginControlLogic initWithData:]:查看偏移基地址: 214 | 215 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/54be932d-6deb-4fec-b076-8494548ae0a9/Untitled.png) 216 | 217 | 则偏移后的地址: 14B6A66 + 30000 = 14E6A66设置断点动态调试,使用: 218 | 219 | ``` 220 | br s -a 0x 14E6A66 221 | ``` 222 | 223 | ### **0x05 Cycript** 224 | 225 | 安装Cycript 226 | 227 | ``` 228 | dpkg -i cycript_0.9.461_iphoneos-arm.deb 229 | dpkg -i libffi_1-3.0.10-5_iphoneos-arm.deb 230 | 231 | cycript -p 232 | ``` 233 | 234 | 步骤: 235 | 236 | 安装cydia之后的ssh,然后mac本机: 237 | 238 | ``` 239 | brew install usbmuxd 240 | iproxy 2222 22 //iphone的22端口转发到本机的2222 241 | ssh root@localhost -p 2222 //默认密码 alpine 242 | ``` 243 | 244 | ``` 245 | cycript: 246 | [UIApp description] 247 | [[UIApp keyWindow] recursiveDescription].toString() //输出如下 248 | > 249 | | | | | | | | | | > 250 | | | | | | | | | | > 251 | 252 | //查看某个UI: 253 | [#0x18b1c070 _ivarDescription].toString() 254 | [#0x15baf520 nextResponder] 某个地址的调用方法 255 | 256 | [[UIApp keyWindow] _autolayoutTrace].toString() 257 | //choose传递一个类,可以在内存中找出属于这个类的对象 258 | //输出对象的属性: 259 | 方法1: 简单基本获取方法。 260 | *controller(直接在对象前面加个*) 261 | 262 | 方法2:方法一无法获取,就使用方法2 263 | [i for (i in *UIApp)] 264 | 265 | 方法3:建议方法三,方法三能获取到更多 266 | function tryPrintIvars(a){ var x={}; for(i in *a){ try{ x[i] = (*a)[i]; } catch(e){} } return x; } 267 | 268 | function printMethods(className, isa) { 269 | var count = new new Type("I"); 270 | var classObj = (isa != undefined) ? objc_getClass(className)->isa : objc_getClass(className); 271 | var methods = class_copyMethodList(classObj, count); 272 | var methodsArray = []; 273 | for(var i = 0; i < *count; i++) { 274 | var method = methods[i]; 275 | methodsArray.push({selector:method_getName(method), implementation:method_getImplementation(method)}); 276 | } 277 | free(methods); 278 | return methodsArray; 279 | } 280 | ``` 281 | 282 | ``` 283 | cycript -p Springboard 或 cycript -p pid 284 | 285 | #在内存中找一个MD5Signater类的实例对象 286 | choose(MD5Signater) 287 | 288 | #调用0x166b4fb0处的对象的show函数 289 | [#0x166b4fb0 show] 290 | 291 | #对show函数传入参数3344 292 | [#0x166b4fb0 show:3344] 293 | 294 | #新建一个MD5Signater类的实例,并调用它的setSecret函数,传入参数1 295 | obj = [MD5Signater alloc] 296 | [#0x146f1a30 setSecret:1] 297 | 298 | 在Objective-C中,[someObject somemethod]的底层实现,实际上是objc_msgSend(someObject,someMethod),其中前一个是Objective-C对象,后者则可以强制转换成一个字符串。 299 | ``` 300 | 301 | 在Objective-C里面,成员方法与类方法的区别: 302 | 303 | - 成员方法是以减号 "-" 开头 //成员方法必须使用对象调用 304 | - 类方法是以加号开头 "+" //类方法可以直接使用类名调用 305 | 306 | ### **Objective-C方法名的问题** 307 | 308 | ``` 309 | - (double)pi; 310 | 311 | 方法名就是pi 312 | 313 | - (int)square:(int)num; 314 | 315 | 带参数的方法名有点特殊,冒号后面一定是参数,可以理解为,有几个冒号就有几个参数,把空格后面到参数前面的内容拼起来就是方法名。所以这个方法名是square:(注意冒号) 316 | 317 | - (int)addNum1:(int)num1 addNum2:(int)num2; 318 | 319 | 根据上面的方法,这个方法名是addNum1:addNum2: 320 | ``` 321 | 322 | 所以根据上面方法名的问题,在cycript里面调用的时候,是这样: 323 | 324 | ``` 325 | cy# choose(PARSEPedometerInfo) 326 | [#"PARSPedometerInfo<0x12f22cd60>: \n integration=1541 \n iPhone=1541 \n watch=0 \n heartRat=0\n at:2017-12-26 16:00:00 +0000",#"PARSPedometerInfo<0x12f406c90>: \n integration=1541 \n iPhone=1541 \n watch=0 \n heartRat=0\n at:2017-12-26 16:00:00 +0000"] 327 | 也即找到两个PARSPedometerInfo类的对象,随便用其中一个即可 328 | [#0x12f22cd60 setIntegratedSteps:66666] 329 | 330 | setIntegratedSteps是减号开头的函数,如果是+号开头的函数用法则[className funcName:6666],如下面的函数是+号开头的函数,可以直接调用这个类中的函数,而不用创建这个类的实例: 331 | cy# [PARSCryptDataUtils encryptWithServerTimestamp:"18013790233"] 332 | 333 | 带减号的函数,要实例化之后才可以调用 334 | 带加号的函数,可以直接调用 335 | ``` 336 | 337 | 这一部分主要参考[http://3xp10it.cc/%E4%BA%8C%E8%BF%9B%E5%88%B6/2017/12/25/ida%E9%80%9A%E8%BF%87usb%E8%B0%83%E8%AF%95ios%E4%B8%8B%E7%9A%84app/](http://3xp10it.cc/%E4%BA%8C%E8%BF%9B%E5%88%B6/2017/12/25/ida%E9%80%9A%E8%BF%87usb%E8%B0%83%E8%AF%95ios%E4%B8%8B%E7%9A%84app/)文章 338 | 339 | ### **0x06 调试流程** 340 | 341 | 如果要使用lldb调试越狱设备上的进程,需要先将connect的端口映射到本地,以1234端口为例: 342 | 343 | ``` 344 | iproxy 1234 1234 345 | 然后打开lldb,输入以下命令: 346 | process connect connect://localhost:1234 347 | 连接越狱设备,输入: 348 | debugserver *:1234 -a 349 | 只要越狱设备上的debugserver(重签名过的)正常运行,就可以通过lldb进行远程调试 350 | ``` 351 | 352 | 越狱设备第一次连接xcode的时候会在/Developer/usr/bin目录下生成一个debugserver,这个debugserver在ios里面运行会失败需要使用ldid签名,需要两个东西: 353 | 354 | - ldid [http://7xibfi.com1.z0.glb.clouddn.com/uploads/default/668/c134605bb19a433f.xml](http://7xibfi.com1.z0.glb.clouddn.com/uploads/default/668/c134605bb19a433f.xml) 355 | - xml(文件) [http://joedj.net/ldid](http://joedj.net/ldid) 356 | 357 | xml文件保存为`ent.xml`,然后签名: 358 | 359 | ``` 360 | ldid -Sent.xml debugserver 361 | ``` 362 | 363 | 然后回传到ios上面即可,使用wget或者scp(scp失败,这里是用的是wget) 364 | 365 | ``` 366 | debugserver 0.0.0.0:1234 "SpringBoard" 367 | (lldb)process connect connect://: 368 | ``` 369 | 370 | 371 | ### **Object-C 的一些基础知识** 372 | 373 | 在Objective-C中的“方法调用”其实应该叫做消息传递。以objc_msgSend函数为例子, 374 | 375 | ``` 376 | [person sayHello] 377 | 可以解释为调用person对象的sayHello方法,但是如果从Object-C的Runtime角度来说,这个代码世纪是在发送一个消息,这个代码,编译器时机会将它转换成这样一个函数调用: 378 | objc_msgSend(person,@selector(sayHello)) 379 | ``` 380 | 381 | 第一个参数是要发送消息的实例,也就是person对象。objc_msgSend会先查询它的methodList方法列表,使用第二个参数sayHello 382 | 383 | ``` 384 | 苹果文档这样写的 385 | id objc_msgSend(id self, SEL _cmd, ...) 386 | ``` 387 | 388 | 将一个消息发送给一个对象,并且返回一个值。其中,self是消息的接受者,_cmd是selector,... 是可变参数列表。 389 | 390 | 在现代操作系统中,一个线程会被分配一个stack,当一个函数被调用,一个stack frame(帧栈)就会被压到stack里,里面包含这个函数设计的参数,局部变量,返回地址等相关信息。当函数返回这个帧栈之后,这个帧栈就会被销毁。 391 | 392 | ``` 393 | _text:0001D76A MOV R0, #(selRefHTTPMethod - 0x1C776) ; selRef_HTTPMethod 394 | _text:0001D772 ADD R0, PC ; selRefHTTPMethod 395 | __text:0001D774 LDR R1, [R0] ; "HTTPMethod" 396 | __text:0001D776 MOV R0, R10 397 | _text:0001D778 STR R1, [SP,#0xAC+varA0] 398 | _text:0001D77A BLX _objcmsgSend 399 | __text:0001D77E MOV R7, R7 400 | _text:0001D780 BLX _objcretainAutoreleasedReturnValue 401 | __text:0001D784 MOV R4, R0 402 | _text:0001D786 MOV R0, #(selRefsetRequestMethod_ - 0x1C794) ; selRef_setRequestMethod_ 403 | __text:0001D78E MOV R2, R4 404 | ``` 405 | 406 | 0001D77A处的selector为HTTPMethod,在functions windows里可以搜到这个函数,函数在执行前把调用的对象存储在R0中。 407 | 408 | ``` 409 | __text:0001D774 LDR R1, [R0] ; "HTTPMethod" //把方法名放到R1中 410 | __text:0001D776 MOV R0, R10 //R0赋值为R10所在的值,此处R10位HTTPMethod这个方法归属的类的指针之类。 411 | 上面两条指令确定了调用的函数,调用完方法,如果一个方法有返回值,会更新在R0,大于一个返回值,就会通过栈来返回值。(意思是如果函数不止一个返回值,就会通过栈来返回) 412 | ``` 413 | 414 | ``` 415 | NSString *string1 = @"test 1"; 416 | NSString *string2 = @"test 2"; 417 | (lldb) po string1 418 | test 1 419 | (lldb) p string1 420 | (NSString *) $2 = 0x0000000100003af0 @"test 1" 421 | (lldb) p string2 422 | (NSString *) $3 = 0x0000000100003b10 @"test 2" 423 | ``` 424 | 425 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/69bba1b4-e1ec-4fbf-be83-654f1662786d/Untitled.png) 426 | 427 | ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9f19aa76-4964-485b-93a0-b97ec35c2194/Untitled.png) 428 | -------------------------------------------------------------------------------- /apiServer.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | # import sys 3 | # sys.path.append("/Users/ethan/whatsapp-py") 4 | 5 | import tornado.web 6 | import tornado.ioloop 7 | import tornado.httpserver 8 | import tornado.options 9 | from tornado.options import options, define 10 | from tornado.web import url, RequestHandler 11 | import re 12 | from tornado import gen 13 | from tornado.concurrent import run_on_executor 14 | from concurrent.futures import ThreadPoolExecutor 15 | import time 16 | import datetime 17 | from login.cli import CliStack 18 | 19 | from login.register import * 20 | from config.v1.config import Config 21 | import os 22 | 23 | define("port", default=8001, type=int, help="run server on the given port.") 24 | 25 | class mobileField(): 26 | REGULAR = "^([1-9]+\\d*)" 27 | 28 | def __init__(self, error_dict=None, required=True): 29 | self.error_dict = {} 30 | if error_dict: 31 | self.error_dict.update(error_dict) 32 | self.required = required 33 | self.error = None 34 | self.is_valid = False 35 | self.value = None 36 | 37 | def validate(self, name, input_value): 38 | if not self.required: 39 | # 如果输入的值可以为空, 40 | self.is_valid = True 41 | self.value = input_value 42 | 43 | else: 44 | # 如果要求输入的值不能为空 45 | if not input_value.strip(): 46 | # 如果我输入的值是空值,然后我就去取error_dict里面required对应的值 47 | if self.error_dict.get("required", None): 48 | self.error = self.error_dict["required"] 49 | else: 50 | # 否则,默认把required赋值到self.error 51 | self.error = "%s is required" % name 52 | else: 53 | # 如果我输入的值不是空值,那么我就要和正则表达式进行比较 54 | ret = re.match(mobileField.REGULAR, input_value) 55 | if ret: 56 | self.is_valid = True 57 | # self.value = ret.group() 58 | self.value = input_value 59 | else: 60 | if self.error_dict.get("valid", None): 61 | self.error = self.error_dict["valid"] 62 | else: 63 | self.error = "%s is invalid" % name 64 | 65 | class BaseForm(object): 66 | def check_valid(self, handle): 67 | flag = True 68 | error_message_dict = {} 69 | success_value_dict = {} 70 | 71 | for key, regular in self.__dict__.items(): 72 | input_value = handle.get_argument(key, default="") 73 | regular.validate(key, input_value) 74 | if regular.is_valid: 75 | success_value_dict[key] = regular.value 76 | else: 77 | error_message_dict[key] = regular.error 78 | flag = False 79 | return flag, success_value_dict, error_message_dict 80 | 81 | class MainForm(BaseForm): 82 | def __init__(self): 83 | self.mobile = mobileField(required=False, error_dict={"required": "手机号", "valid": "格式错误"}) 84 | self.countryCode = mobileField(required=False, error_dict={"required": "国家码", "valid": "格式错误"}) 85 | self.ipAgent = mobileField(required=False, error_dict={"valid": "格式错误"}) 86 | self.ipAgentName = mobileField(required=False, error_dict={"valid": "格式错误"}) 87 | self.ipAgentPassword = mobileField(required=False, error_dict={"valid": "格式错误"}) 88 | 89 | self.SMSCode = mobileField(required=False, error_dict={"valid": "格式错误"}) 90 | 91 | self.toMobile = mobileField(required=False, error_dict={"valid": "格式错误"}) 92 | self.toText = mobileField(required=False, error_dict={"valid": "格式错误"}) 93 | 94 | self.groupUrl = mobileField(required=False, error_dict={"valid": "格式错误"}) 95 | 96 | 97 | class IndexHandler(RequestHandler): 98 | def get(self): 99 | self.render("index.html") 100 | 101 | 102 | #获取验证码 103 | class getCodeHandler(RequestHandler): 104 | def initialize(self, subject): 105 | self.subject = subject 106 | 107 | def get(self): 108 | obj = MainForm() 109 | is_valid, success_dict, error_dict = obj.check_valid(self) 110 | if is_valid: 111 | print("success", success_dict) 112 | resu = {'code': 200, 'message': '成功'} 113 | 114 | config = Config() 115 | 116 | config.phone = success_dict['mobile'] 117 | config.cc = success_dict['countryCode'] 118 | config.sim_mcc = search_mcc(config.cc) 119 | config.sim_mnc = "01" 120 | p_in = str(config.phone)[len(str(config.cc)):] 121 | 122 | profile = Profile(config.phone) 123 | if profile.config is None: 124 | config.pushname = "steatt" 125 | 126 | if not profile.config or not profile.config.edge_routing_info: 127 | r = register(config) 128 | result = r.handleRequestCode(method="sms", config=r._config) 129 | self.write(result) 130 | else: 131 | self.write({'code': 1001, 'message':'本地已有登录数据可直接登录'}) 132 | 133 | else: 134 | print("error", error_dict) 135 | self.render("index.html", error_dict=error_dict) 136 | 137 | class registerHandler(RequestHandler): 138 | executor = ThreadPoolExecutor(4) 139 | 140 | def initialize(self, subject): 141 | self.subject = subject 142 | 143 | @gen.coroutine 144 | def get(self): 145 | obj = MainForm() 146 | is_valid, success_dict, error_dict = obj.check_valid(self) 147 | if is_valid: 148 | print("success", success_dict) 149 | 150 | config = Config() 151 | 152 | config.phone = success_dict['mobile'] 153 | config.cc = success_dict['countryCode'] 154 | config.sim_mcc = search_mcc(config.cc) 155 | config.sim_mnc = "01" 156 | smsCode = success_dict['SMSCode'] 157 | 158 | profile = Profile(config.phone) 159 | if profile.config is None: 160 | config.pushname = "stedgg" 161 | 162 | if not profile.config or not profile.config.edge_routing_info: 163 | r = register(profile.config) 164 | result = r.handleRegister(code=smsCode, config=r._config) 165 | self.write(result) 166 | #注册成功后登录 167 | try: 168 | start = time.time() 169 | # 并行执行 170 | result1, result2 = yield gen.with_timeout(datetime.timedelta(seconds=300), 171 | [self.login(config.phone), self.doing()], 172 | quiet_exceptions=tornado.gen.TimeoutError) 173 | print(result1, result2) 174 | print(time.time() - start) 175 | except gen.TimeoutError: 176 | print("Timeout") 177 | 178 | else: 179 | self.write({'code': 1001, 'message': '本地已有登录数据可直接登录'}) 180 | #注册成功后登录 181 | try: 182 | start = time.time() 183 | # 并行执行 184 | result1, result2 = yield gen.with_timeout(datetime.timedelta(seconds=300), 185 | [self.login(config.phone), self.doing()], 186 | quiet_exceptions=tornado.gen.TimeoutError) 187 | print(result1, result2) 188 | print(time.time() - start) 189 | except gen.TimeoutError: 190 | print("Timeout") 191 | 192 | else: 193 | print("error", error_dict) 194 | self.render("index.html", error_dict=error_dict) 195 | 196 | @run_on_executor 197 | def login(self, mobile): 198 | profile = Profile(mobile) 199 | stack = CliStack(profile) 200 | stack.connect() 201 | return {'code': 0, 'message': '完成注册登录'} 202 | 203 | @run_on_executor 204 | def doing(self): 205 | return "2" 206 | 207 | class messageHandler(RequestHandler): 208 | executor = ThreadPoolExecutor(4) 209 | 210 | @gen.coroutine 211 | def get(self): 212 | obj = MainForm() 213 | is_valid, success_dict, error_dict = obj.check_valid(self) 214 | if is_valid: 215 | profile = Profile(success_dict['mobile']) 216 | if profile.config and profile.config.edge_routing_info: 217 | try: 218 | start = time.time() 219 | result = yield gen.with_timeout(datetime.timedelta(seconds=300), 220 | [self.sendMessage(profile.config.phone, success_dict['toMobile'], success_dict['toText'])], 221 | quiet_exceptions=tornado.gen.TimeoutError) 222 | self.write(result[0]) 223 | print(time.time() - start) 224 | except gen.TimeoutError: 225 | print("Timeout") 226 | 227 | else: 228 | self.write({'code': 1002, 'message': '登录数据已失效请重新登录'}) 229 | 230 | else: 231 | print("error", error_dict) 232 | self.render("index.html", error_dict=error_dict) 233 | 234 | @run_on_executor 235 | def sendMessage(self, mobile, to, text): 236 | profile = Profile(mobile) 237 | stack = CliStack(profile) 238 | stack.sendMessages(to, text) 239 | return {'code': 0, 'message': '发送完成'} 240 | 241 | class GroupJoin(RequestHandler): 242 | executor = ThreadPoolExecutor(4) 243 | 244 | @gen.coroutine 245 | def get(self): 246 | obj = MainForm() 247 | is_valid, success_dict, error_dict = obj.check_valid(self) 248 | if is_valid: 249 | groupUrl = success_dict['groupUrl'] 250 | profile = Profile('6281995412652') 251 | 252 | if profile.config and profile.config.edge_routing_info: 253 | try: 254 | start = time.time() 255 | result = yield gen.with_timeout(datetime.timedelta(seconds=300), 256 | [self.groupJoinUrl(profile.config.phone, groupUrl)], 257 | quiet_exceptions=tornado.gen.TimeoutError) 258 | self.write(result[0]) 259 | print(time.time() - start) 260 | except gen.TimeoutError: 261 | print("Timeout") 262 | 263 | else: 264 | self.write({'code': 1002, 'message': '登录数据已失效请重新登录'}) 265 | 266 | else: 267 | print("error", error_dict) 268 | self.render("index.html", error_dict=error_dict) 269 | 270 | @run_on_executor 271 | def groupJoinUrl(self, mobile, groupUrl): 272 | profile = Profile(mobile) 273 | stack = CliStack(profile) 274 | stack.groupInfoList(groupUrl) 275 | 276 | return {'code': 0, 'message': '完成注册登录'} 277 | 278 | import pandas as pd 279 | def search_mcc(countryCode='86'): 280 | df = pd.read_csv(os.path.abspath('.') + '/countries.tsv', header=None, encoding='utf-8', delimiter='\t', skiprows=1) 281 | line = df[df[2] == int(countryCode)] 282 | countryCode = line[3] 283 | mcc = "460" 284 | for i, v in countryCode.items(): 285 | mcc = v 286 | break 287 | print ("mcc = " + mcc) 288 | return mcc.split(",")[0] 289 | 290 | class Test(RequestHandler): 291 | 292 | @gen.coroutine 293 | def get(self): 294 | print(self.get_argument("tag"), self.get_argument("time")) 295 | self.finish({"tag":self.get_argument("tag"),'time': self.get_argument("time")}) 296 | 297 | 298 | # /api/WhatsAppProtocol/SendSMSCode 299 | if __name__ == "__main__": 300 | tornado.options.parse_command_line() 301 | app = tornado.web.Application([ 302 | (r"/", IndexHandler), 303 | (r"/api/WhatsAppProtocol/SendSMSCode", getCodeHandler, {"subject": "success"}), 304 | (r"/api/WhatsAppProtocol/Register", registerHandler, {"subject": "success"}), 305 | (r"/api/WhatsAppProtocol/SendText", messageHandler), 306 | (r"/api/WhatsAppProtocol/GroupJoin", GroupJoin), 307 | (r"/api/test", Test), 308 | 309 | ], 310 | debug=False) 311 | 312 | http_server = tornado.httpserver.HTTPServer(app) 313 | http_server.listen(options.port) 314 | tornado.ioloop.IOLoop.current().start() 315 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger(__name__) 4 | ch = logging.StreamHandler() 5 | # ch.setLevel(logging.DEBUG) 6 | 7 | formatter = logging.Formatter('%(levelname).1s %(asctime)s %(name)s - %(message)s') 8 | 9 | ch.setFormatter(formatter) 10 | 11 | logger.addHandler(ch) -------------------------------------------------------------------------------- /config/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /config/__pycache__/enumExec.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/__pycache__/enumExec.cpython-38.pyc -------------------------------------------------------------------------------- /config/__pycache__/logger.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/__pycache__/logger.cpython-38.pyc -------------------------------------------------------------------------------- /config/__pycache__/manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/__pycache__/manager.cpython-38.pyc -------------------------------------------------------------------------------- /config/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/base/__init__.py -------------------------------------------------------------------------------- /config/base/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/base/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /config/base/__pycache__/config.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/base/__pycache__/config.cpython-38.pyc -------------------------------------------------------------------------------- /config/base/__pycache__/serialize.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/base/__pycache__/serialize.cpython-38.pyc -------------------------------------------------------------------------------- /config/base/__pycache__/transform.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/base/__pycache__/transform.cpython-38.pyc -------------------------------------------------------------------------------- /config/base/config.py: -------------------------------------------------------------------------------- 1 | 2 | class Config(object): 3 | def __init__(self, version): 4 | self._version = version 5 | 6 | def __contains__(self, item): 7 | return self[item] is not None 8 | 9 | def __getitem__(self, item): 10 | return getattr(self, "_%s" % item) 11 | 12 | def __setitem__(self, key, value): 13 | setattr(self, key, value) 14 | 15 | def keys(self): 16 | return [var[1:] for var in vars(self)] 17 | 18 | @property 19 | def version(self): 20 | return self._version 21 | -------------------------------------------------------------------------------- /config/base/serialize.py: -------------------------------------------------------------------------------- 1 | class ConfigSerialize(object): 2 | 3 | def __init__(self, transforms): 4 | self._transforms = transforms 5 | 6 | def serialize(self, config): 7 | """ 8 | :param config: 9 | :type config: Config 10 | :return: 11 | :rtype: bytes 12 | """ 13 | for transform in self._transforms: 14 | config = transform.transform(config) 15 | return config 16 | 17 | def deserialize(self, data): 18 | """ 19 | :type cls: type 20 | :param data: 21 | :type data: bytes 22 | :return: 23 | :rtype: Config 24 | """ 25 | for transform in self._transforms[::-1]: 26 | data = transform.reverse(data) 27 | return data 28 | -------------------------------------------------------------------------------- /config/base/transform.py: -------------------------------------------------------------------------------- 1 | class ConfigTransform(object): 2 | def transform(self, config): 3 | """ 4 | :param config: 5 | :type config: Config 6 | :return: dict 7 | :rtype: 8 | """ 9 | 10 | def reverse(self, data): 11 | """ 12 | :param data: 13 | :type data: 14 | :return: 15 | :rtype: Config 16 | """ 17 | -------------------------------------------------------------------------------- /config/config.py: -------------------------------------------------------------------------------- 1 | from config.base import config 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | class Config(object): 6 | def __init__(self, version): 7 | self._version = version 8 | 9 | def __contains__(self, item): 10 | return self[item] is not None 11 | 12 | def __getitem__(self, item): 13 | return getattr(self, "_%s" % item) 14 | 15 | def __setitem__(self, key, value): 16 | setattr(self, key, value) 17 | 18 | def keys(self): 19 | return [var[1:] for var in vars(self)] 20 | 21 | @property 22 | def version(self): 23 | return self._version 24 | 25 | 26 | class Config(config.Config): 27 | def __init__( 28 | self, 29 | phone=None, 30 | cc=None, 31 | password=None, 32 | pushname=None, 33 | id=None, 34 | mcc=None, 35 | mnc=None, 36 | client_static_keypair=None, 37 | server_static_public=None, 38 | expid=None, 39 | fdid=None, 40 | edge_routing_info=None, 41 | chat_dns_domain=None 42 | ): 43 | super(Config, self).__init__(1) 44 | 45 | self._phone = str(phone) if phone is not None else None # type: str 46 | self._cc = cc # type: int 47 | self._password = password # type: str 48 | self._pushname = pushname # type: str 49 | self._id = id 50 | self._client_static_keypair = client_static_keypair 51 | self._server_static_public = server_static_public 52 | self._expid = expid 53 | self._fdid = fdid 54 | self._mcc = mcc 55 | self._mnc = mnc 56 | self._edge_routing_info = edge_routing_info 57 | self._chat_dns_domain = chat_dns_domain 58 | 59 | if self._password is not None: 60 | logger.warn("Setting a password in Config is deprecated and not used anymore. " 61 | "client_static_keypair is used instead") 62 | 63 | def __str__(self): 64 | from config.v1.serialize import ConfigSerialize 65 | from config.transforms.dict_json import DictJsonTransform 66 | return DictJsonTransform().transform(ConfigSerialize(self.__class__).serialize(self)) 67 | 68 | @property 69 | def phone(self): 70 | return self._phone 71 | 72 | @phone.setter 73 | def phone(self, value): 74 | self._phone = str(value) if value is not None else None 75 | 76 | @property 77 | def cc(self): 78 | return self._cc 79 | 80 | @cc.setter 81 | def cc(self, value): 82 | self._cc = value 83 | 84 | @property 85 | def password(self): 86 | return self._password 87 | 88 | @password.setter 89 | def password(self, value): 90 | self._password = value 91 | if value is not None: 92 | logger.warn("Setting a password in Config is deprecated and not used anymore. " 93 | "client_static_keypair is used instead") 94 | 95 | @property 96 | def pushname(self): 97 | return self._pushname 98 | 99 | @pushname.setter 100 | def pushname(self, value): 101 | self._pushname = value 102 | 103 | @property 104 | def mcc(self): 105 | return self._mcc 106 | 107 | @mcc.setter 108 | def mcc(self, value): 109 | self._mcc = value 110 | 111 | @property 112 | def mnc(self): 113 | return self._mnc 114 | 115 | @mnc.setter 116 | def mnc(self, value): 117 | self._mnc = value 118 | 119 | @property 120 | def id(self): 121 | return self._id 122 | 123 | @id.setter 124 | def id(self, value): 125 | self._id = value 126 | 127 | @property 128 | def client_static_keypair(self): 129 | return self._client_static_keypair 130 | 131 | @client_static_keypair.setter 132 | def client_static_keypair(self, value): 133 | self._client_static_keypair = value 134 | 135 | @property 136 | def server_static_public(self): 137 | return self._server_static_public 138 | 139 | @server_static_public.setter 140 | def server_static_public(self, value): 141 | self._server_static_public = value 142 | 143 | @property 144 | def expid(self): 145 | return self._expid 146 | 147 | @expid.setter 148 | def expid(self, value): 149 | self._expid = value 150 | 151 | @property 152 | def fdid(self): 153 | return self._fdid.upper() 154 | 155 | @fdid.setter 156 | def fdid(self, value): 157 | self._fdid = value 158 | 159 | @property 160 | def edge_routing_info(self): 161 | return self._edge_routing_info 162 | 163 | @edge_routing_info.setter 164 | def edge_routing_info(self, value): 165 | self._edge_routing_info = value 166 | 167 | @property 168 | def chat_dns_domain(self): 169 | return self._chat_dns_domain 170 | 171 | @chat_dns_domain.setter 172 | def chat_dns_domain(self, value): 173 | self._chat_dns_domain = value 174 | -------------------------------------------------------------------------------- /config/enumExec.py: -------------------------------------------------------------------------------- 1 | 2 | from enum import Enum 3 | # 需要执行的功能 4 | class ExecTypes(Enum): 5 | login = 0b1 6 | group_join = 0b1 << 1 7 | group_info = 0b1 << 2 8 | group_kick = 0b1 << 3 9 | 10 | send_text = 0b1 << 4 11 | receive_text = 0b1 << 5 12 | others = 0b1 << 6 13 | -------------------------------------------------------------------------------- /config/logger.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import re, time 5 | from logging import Logger 6 | from logging.handlers import TimedRotatingFileHandler 7 | 8 | from env.env_app import app_env 9 | 10 | currentPath = os.path.dirname(__file__) + '/logs/' 11 | # currentPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/logs' 12 | 13 | class FinalLogger(): 14 | 15 | def init_logger(self, logger_name): 16 | 17 | if logger_name not in Logger.manager.loggerDict: 18 | logger = logging.getLogger(logger_name) 19 | logger.setLevel(logging.DEBUG) 20 | log_path = os.path.join(currentPath, logger_name) 21 | msg = { 22 | "app": os.getenv('APP'), 23 | "@timestamp": "%(asctime)s", 24 | "date": "%(asctime)s", 25 | "logLevel": "%(levelname)s", 26 | "file": "%(pathname)s:%(lineno)d", 27 | "msg": "%(message)s" 28 | } 29 | format_str = json.dumps(msg) 30 | formatter = logging.Formatter(format_str, '%Y-%m-%dT%H:%M:%SZ') 31 | 32 | fileHandler = TimedRotatingFileHandler(filename=log_path, when="midnight", backupCount=7) 33 | fileHandler.suffix = "%Y-%m-%d.log" 34 | fileHandler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}.log$") 35 | fileHandler.setFormatter(formatter) 36 | logger.addHandler(fileHandler) 37 | 38 | logger = logging.getLogger(logger_name) 39 | return logger 40 | 41 | class FinalLogger2(): 42 | 43 | def init_logger(self, logger_name1=None): 44 | 45 | num = app_env() 46 | if num == 1: 47 | logger_name = "1546944914" 48 | elif num == 2: 49 | logger_name = "4615761317" 50 | elif num == 3: 51 | logger_name = "6412722277" 52 | else: 53 | logger_name = "8728356502" 54 | 55 | logger_name = time.strftime('%Y-%m-%d', time.localtime()) + "--" + logger_name + "-" + "Info" + '.log' 56 | 57 | if logger_name not in Logger.manager.loggerDict: 58 | logger = logging.getLogger(logger_name) 59 | logger.setLevel(logging.DEBUG) 60 | 61 | # allfilename = time.strftime('%Y-%m-%d', time.localtime()) + "--" + logger_name + "-" + "Info" + '.log' 62 | all_log_file = os.path.join(currentPath, logger_name) 63 | msg = { 64 | "app": os.getenv('APP'), 65 | "@timestamp": "%(asctime)s", 66 | "date": "%(asctime)s", 67 | "logLevel": "%(levelname)s", 68 | "file": "%(pathname)s:%(lineno)d", 69 | "msg": "%(message)s", 70 | } 71 | format_str = json.dumps(msg) 72 | 73 | formatter = logging.Formatter(format_str, '%Y-%m-%dT%H:%M:%SZ') 74 | fileHandler = logging.FileHandler(all_log_file) 75 | fileHandler.setFormatter(formatter) 76 | logger.addHandler(fileHandler) 77 | 78 | logger = logging.getLogger(logger_name) 79 | return logger 80 | 81 | class FinalLogger1(): 82 | 83 | def init_logger(self, logger_name): 84 | 85 | num = app_env() 86 | if num == 1: 87 | logger_name = "1546944914" 88 | elif num == 2: 89 | logger_name = "4615761317" 90 | elif num == 3: 91 | logger_name = "6412722277" 92 | else: 93 | logger_name = "8728356502" 94 | 95 | if logger_name not in Logger.manager.loggerDict: 96 | 97 | logger = logging.getLogger(logger_name) 98 | logger.setLevel(logging.DEBUG) 99 | 100 | allfilename = time.strftime('%Y-%m-%d', time.localtime()) + "--" + logger_name + "-" + "Info" + '.log' 101 | all_log_file = os.path.join(currentPath, allfilename) 102 | 103 | msg = { 104 | "app": os.getenv('APP'), 105 | "@timestamp": "%(asctime)s", 106 | "date": "%(asctime)s", 107 | "logLevel": "%(levelname)s", 108 | "file": "%(pathname)s:%(lineno)d", 109 | "msg": "%(message)s", 110 | } 111 | format_str = json.dumps(msg) 112 | formatter = logging.Formatter(format_str, '%Y-%m-%dT%H:%M:%SZ') 113 | 114 | all_log_handler = TimedRotatingFileHandler(all_log_file, when='midnight', backupCount=7) 115 | all_log_handler.setFormatter(formatter) 116 | all_log_handler.setLevel(logging.INFO) 117 | logger.addHandler(all_log_handler) 118 | 119 | logger = logging.getLogger(logger_name) 120 | return logger 121 | -------------------------------------------------------------------------------- /config/manager.py: -------------------------------------------------------------------------------- 1 | from config.v1.config import Config 2 | from config.transforms.dict_keyval import DictKeyValTransform 3 | from config.transforms.dict_json import DictJsonTransform 4 | from config.v1.serialize import ConfigSerialize 5 | from common.tools import StorageTools 6 | import logging 7 | import os 8 | from mysql.execSql import * 9 | import base64 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class ConfigManager(object): 15 | NAME_FILE_CONFIG = "config" 16 | 17 | TYPE_KEYVAL = 1 18 | TYPE_JSON = 2 19 | 20 | TYPE_NAMES = { 21 | TYPE_KEYVAL: "keyval", 22 | TYPE_JSON: "json" 23 | } 24 | 25 | MAP_EXT = { 26 | "yo": TYPE_KEYVAL, 27 | "json": TYPE_JSON, 28 | } 29 | 30 | TYPES = { 31 | TYPE_KEYVAL: DictKeyValTransform, 32 | TYPE_JSON: DictJsonTransform 33 | } 34 | 35 | def load(self, path_or_profile_name, profile_only=False, fromNet=False): 36 | # type: (str, bool) -> Config 37 | """ 38 | Will first try to interpret path_or_profile_name as direct path to a config file and load from there. If 39 | this fails will interpret it as profile name and load from profile dir. 40 | :param path_or_profile_name: 41 | :param profile_only 42 | :return Config instance, or None if no config could be found 43 | """ 44 | logger.debug("load(path_or_profile_name=%s, profile_only=%s)" % (path_or_profile_name, profile_only)) 45 | 46 | exhausted = [] 47 | if not profile_only: 48 | if fromNet: 49 | mobile = path_or_profile_name 50 | sql = "SELECT config FROM phone_conf WHERE id={m_value}".format(m_value=mobile) 51 | execSql = ExecuteSQL(MYSQL_NAME) 52 | results = execSql.fetch_sql(sql) 53 | execSql.db_close() 54 | 55 | if len(results): 56 | r = results[0] 57 | config = str(r[0]) 58 | data = str(base64.b64decode(config), "utf-8") 59 | datadict = self.TYPES[2]().reverse(data) 60 | 61 | config = self.load_data(datadict) 62 | else: 63 | print(mobile + ":mysql config为空!") 64 | config = self._load_path(path_or_profile_name) 65 | 66 | else: 67 | config = self._load_path(path_or_profile_name) 68 | else: 69 | config = None 70 | if config is not None: 71 | return config 72 | else: 73 | logger.debug("path_or_profile_name is not a path, using it as profile name") 74 | if not profile_only: 75 | exhausted.append(path_or_profile_name) 76 | profile_name = path_or_profile_name 77 | config_dir = StorageTools.getStorageForProfile(profile_name) 78 | logger.debug("Detecting config for profile=%s, dir=%s" % (profile_name, config_dir)) 79 | for ftype in self.MAP_EXT: 80 | if len(ftype): 81 | fname = (self.NAME_FILE_CONFIG + "." + ftype) 82 | else: 83 | fname = self.NAME_FILE_CONFIG 84 | 85 | fpath = os.path.join(config_dir, fname) 86 | logger.debug("Trying %s" % fpath) 87 | if os.path.isfile(fpath): 88 | return self._load_path(fpath) 89 | 90 | exhausted.append(fpath) 91 | 92 | logger.error("Could not find a config for profile=%s, paths checked: %s" % (profile_name, ":".join(exhausted))) 93 | 94 | def _type_to_str(self, type): 95 | """ 96 | :param type: 97 | :type type: int 98 | :return: 99 | :rtype: 100 | """ 101 | for key, val in self.TYPE_NAMES.items(): 102 | if key == type: 103 | return val 104 | 105 | def _load_path(self, path): 106 | """ 107 | :param path: 108 | :type path: 109 | :return: 110 | :rtype: 111 | """ 112 | logger.debug("_load_path(path=%s)" % path) 113 | if os.path.isfile(path): 114 | configtype = self.guess_type(path) 115 | logger.debug("Detected config type: %s" % self._type_to_str(configtype)) 116 | if configtype in self.TYPES: 117 | logger.debug("Opening config for reading") 118 | with open(path, 'r') as f: 119 | data = f.read() 120 | datadict = self.TYPES[configtype]().reverse(data) 121 | return self.load_data(datadict) 122 | else: 123 | raise ValueError("Unsupported config type") 124 | else: 125 | logger.debug("_load_path couldn't find the path: %s" % path) 126 | 127 | 128 | def load_data(self, datadict): 129 | logger.debug("Loading config") 130 | return ConfigSerialize(Config).deserialize(datadict) 131 | 132 | def guess_type(self, config_path): 133 | dissected = os.path.splitext(config_path) 134 | if len(dissected) > 1: 135 | ext = dissected[1][1:].lower() 136 | config_type = self.MAP_EXT[ext] if ext in self.MAP_EXT else None 137 | else: 138 | config_type = None 139 | 140 | if config_type is not None: 141 | return config_type 142 | else: 143 | logger.debug("Trying auto detect config type by parsing") 144 | with open(config_path, 'r') as f: 145 | data = f.read() 146 | for config_type, transform in self.TYPES.items(): 147 | config_type_str = self.TYPE_NAMES[config_type] 148 | try: 149 | logger.debug("Trying to parse as %s" % config_type_str) 150 | if transform().reverse(data): 151 | logger.debug("Successfully detected %s as config type for %s" % (config_type_str, config_path)) 152 | return config_type 153 | except Exception as ex: 154 | logger.debug("%s was not parseable as %s, reason: %s" % (config_path, config_type_str, ex)) 155 | 156 | def get_str_transform(self, serialize_type): 157 | if serialize_type in self.TYPES: 158 | return self.TYPES[serialize_type]() 159 | 160 | def config_to_str(self, config, serialize_type=TYPE_JSON): 161 | transform = self.get_str_transform(serialize_type) 162 | if transform is not None: 163 | return transform.transform(ConfigSerialize(config.__class__).serialize(config)) 164 | 165 | raise ValueError("unrecognized serialize_type=%d" % serialize_type) 166 | 167 | def save(self, profile_name, config, serialize_type=TYPE_JSON, dest=None): 168 | outputdata = self.config_to_str(config, serialize_type) 169 | 170 | if dest is None: 171 | StorageTools.writeProfileConfig(profile_name, outputdata) 172 | else: 173 | with open(dest, 'wb') as outputfile: 174 | outputfile.write(outputdata) 175 | 176 | def uploadData(self, phone): 177 | config = self.load(phone) 178 | outputdata = self.config_to_str(config) 179 | 180 | configData = str(base64.b64encode(outputdata.encode("utf-8")), "utf-8") 181 | import time 182 | localTime = time.localtime(time.time()) 183 | strTime = time.strftime("%Y-%m-%d %H:%M:%S", localTime) 184 | execSql = ExecuteSQL() 185 | actionValue = (phone, strTime, configData) 186 | sqlInsert = "INSERT INTO wa_users_data (phone, time, config) VALUES {values} ON DUPLICATE KEY UPDATE config='{c_value}'".format( 187 | values=actionValue, c_value=configData) 188 | execSql.execute_sql(sqlInsert) 189 | execSql.db_close() 190 | 191 | def downloadData(self, phone): 192 | sql = "SELECT config FROM wa_users_data WHERE phone={m_value}".format(m_value=phone) 193 | execSql = ExecuteSQL() 194 | results = execSql.fetch_sql(sql) 195 | execSql.db_close() 196 | 197 | if len(results): 198 | r = results[0] 199 | config = str(r[0]) 200 | data = str(base64.b64decode(config), "utf-8") 201 | datadict = self.TYPES[2]().reverse(data) 202 | 203 | config = self.load_data(datadict) 204 | self.save(phone, config) 205 | return config 206 | else: 207 | return None 208 | # { 209 | # "__version__": 1, 210 | # "cc": "62", 211 | # "client_static_keypair": "qFrn5lw9208ixtrqeH4Ll91UBeuyk/1wJjc8nFHR+ExDqy/1UsUUUAaEO0c4B86Vr68uFjbTO/enrJyWPIl3Bw==", 212 | # "country": "id", 213 | # "edge_routing_info": "CAIIDQ==", 214 | # "expid": "nYEJmaIQSmyg7A/hnDlvmw==", 215 | # "fdid": "a5bc7fbc-4f55-4c1e-b95b-c7b8ffee9893", 216 | # "id": "b2O9CbIxNK9cSEXU0js/gVXvnlc=", 217 | # "login": "6283182456634", 218 | # "phone": "6283182456634", 219 | # "pushname": "Fterri Mremington", 220 | # "server_static_public": "xDn6MqBPn3O6ptDhPQt/tqcXrv2dK7aR//NQLFIVal0=", 221 | # "sim_mcc": "510", 222 | # "sim_mnc": "001" 223 | # } 224 | def saveLoginData(self, phone, datadict, loginData): 225 | config = self.load_data(datadict) 226 | outputdata = self.config_to_str(config) 227 | 228 | configData = str(base64.b64encode(outputdata.encode("utf-8")), "utf-8") 229 | import time 230 | localTime = time.localtime(time.time()) 231 | strTime = time.strftime("%Y-%m-%d %H:%M:%S", localTime) 232 | import json 233 | jsonData=json.dumps(loginData) 234 | actionValue = (phone, strTime, configData, datadict['env_name'], jsonData) 235 | 236 | 237 | execSql = ExecuteSQL() 238 | sqlInsert = "INSERT INTO wa_users_data (phone, time, config, env, data) VALUES {values} ON DUPLICATE KEY UPDATE config='{c_value}', time='{time}', data='{data}'".format( 239 | values=actionValue, c_value=configData, time=strTime, data=jsonData) 240 | execSql.execute_sql(sqlInsert) 241 | 242 | deviceInfo = SingleonConfig() 243 | 244 | values = (deviceInfo.deviceId, deviceInfo.versionId, loginData['phone'], loginData['nickName'], loginData['cc'], loginData['country'], strTime, strTime, "") 245 | sql = "INSERT INTO wa_users (device_id, version_id, phone, name, cc, country, rtime, utime, exec) VALUES {values}".format(values=values) 246 | execSql.execute_sql(sql) 247 | execSql.db_close() 248 | 249 | self.save(phone, config) 250 | return config 251 | -------------------------------------------------------------------------------- /config/transforms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__init__.py -------------------------------------------------------------------------------- /config/transforms/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/config_dict.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/config_dict.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/dict_json.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/dict_json.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/dict_keyval.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/dict_keyval.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/filter.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/filter.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/map.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/map.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/meta.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/meta.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/__pycache__/props.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/transforms/__pycache__/props.cpython-38.pyc -------------------------------------------------------------------------------- /config/transforms/config_dict.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | 3 | 4 | class ConfigDictTransform(ConfigTransform): 5 | def __init__(self, cls): 6 | self._cls = cls 7 | 8 | def transform(self, config): 9 | """ 10 | :param config: 11 | :type config: dict 12 | :return: 13 | :rtype: Config 14 | """ 15 | out = {} 16 | for prop in vars(config): 17 | out[prop] = getattr(config, prop) 18 | return out 19 | 20 | def reverse(self, data): 21 | """ 22 | :param data: 23 | :type data: config.config.Config 24 | :return: 25 | :rtype: dict 26 | """ 27 | return self._cls(**data) 28 | -------------------------------------------------------------------------------- /config/transforms/dict_json.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | import json 3 | 4 | 5 | class DictJsonTransform(ConfigTransform): 6 | def transform(self, data): 7 | return json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')) 8 | 9 | def reverse(self, data): 10 | return json.loads(data) 11 | 12 | -------------------------------------------------------------------------------- /config/transforms/dict_keyval.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | 3 | 4 | class DictKeyValTransform(ConfigTransform): 5 | def transform(self, data): 6 | """ 7 | :param data: 8 | :type data: dict 9 | :return: 10 | :rtype: 11 | """ 12 | out=[] 13 | keys = sorted(data.keys()) 14 | for k in keys: 15 | out.append("%s=%s" % (k, data[k])) 16 | return "\n".join(out) 17 | 18 | def reverse(self, data): 19 | out = {} 20 | for l in data.split('\n'): 21 | line = l.strip() 22 | if len(line) and line[0] not in ('#',';'): 23 | prep = line.split('#', 1)[0].split(';', 1)[0].split('=', 1) 24 | varname = prep[0].strip() 25 | val = prep[1].strip() 26 | out[varname.replace('-', '_')] = val 27 | return out 28 | -------------------------------------------------------------------------------- /config/transforms/filter.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | 3 | 4 | class FilterTransform(ConfigTransform): 5 | 6 | def __init__(self, transform_filter=None, reverse_filter=None): 7 | """ 8 | :param transform_filter: 9 | :type transform_filter: function | None 10 | :param reverse_filter: 11 | :type reverse_filter: function | None 12 | """ 13 | self._transform_filter = transform_filter # type: function | None 14 | self._reverse_filter = reverse_filter # type: function | None 15 | 16 | def transform(self, data): 17 | if self._transform_filter is not None: 18 | out = {} 19 | for key, val in data.items(): 20 | if self._transform_filter(key, val): 21 | out[key] = val 22 | return out 23 | return data 24 | 25 | def reverse(self, data): 26 | if self._reverse_filter is not None: 27 | out = {} 28 | for key, val in data.items(): 29 | if self._reverse_filter(key, val): 30 | out[key] = val 31 | return out 32 | return data 33 | -------------------------------------------------------------------------------- /config/transforms/map.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | 3 | 4 | class MapTransform(ConfigTransform): 5 | 6 | def __init__(self, transform_map=None, reverse_map=None): 7 | """ 8 | :param transform_map: 9 | :type transform_map: function | None 10 | :param reverse_map: 11 | :type reverse_map: function | None 12 | """ 13 | self._transform_map = transform_map # type: function | None 14 | self._reverse_map = reverse_map # type: function | None 15 | 16 | def transform(self, data): 17 | if self._transform_map is not None: 18 | out = {} 19 | for key, val in data.items(): 20 | key, val = self._transform_map(key, val) 21 | out[key] = val 22 | return out 23 | return data 24 | 25 | def reverse(self, data): 26 | if self._reverse_map is not None: 27 | out = {} 28 | for key, val in data.items(): 29 | key, val = self._reverse_map(key, val) 30 | out[key] = val 31 | return out 32 | return data 33 | -------------------------------------------------------------------------------- /config/transforms/meta.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | from config.transforms.props import PropsTransform 3 | 4 | 5 | class MetaPropsTransform(ConfigTransform): 6 | META_FORMAT = "__%s__" 7 | 8 | def __init__(self, meta_props=None, meta_format=META_FORMAT): 9 | meta_props = meta_props or () 10 | meta_format = meta_format 11 | transform_map = {} 12 | reverse_map = {} 13 | for prop in meta_props: 14 | formatted = meta_format % prop 15 | transform_map[prop] = lambda key, val, formatted=formatted: (formatted, val) 16 | reverse_map[formatted] = lambda key, val, prop=prop: (prop, val) 17 | 18 | self._props_transform = PropsTransform(transform_map=transform_map, reverse_map=reverse_map) 19 | 20 | def transform(self, data): 21 | return self._props_transform.transform(data) 22 | 23 | def reverse(self, data): 24 | return self._props_transform.reverse(data) 25 | -------------------------------------------------------------------------------- /config/transforms/props.py: -------------------------------------------------------------------------------- 1 | from config.base.transform import ConfigTransform 2 | import types 3 | 4 | 5 | class PropsTransform(ConfigTransform): 6 | def __init__(self, transform_map=None, reverse_map=None): 7 | self._transform_map = transform_map or {} 8 | self._reverse_map = reverse_map or {} 9 | 10 | def transform(self, data): 11 | """ 12 | :param data: 13 | :type data: dict 14 | :return: 15 | :rtype: dict 16 | """ 17 | out = {} 18 | for key, val in data.items(): 19 | if key in self._transform_map: 20 | target = self._transform_map[key] 21 | key, val = target(key, val) if type(target) == types.FunctionType else (key, target) 22 | 23 | out[key] = val 24 | 25 | 26 | return out 27 | 28 | def reverse(self, data): 29 | transformed_dict = {} 30 | 31 | for key, val in data.items(): 32 | if key in self._reverse_map: 33 | target = self._reverse_map[key] 34 | key, val = target(key, val) if type(target) == types.FunctionType else (key, target) 35 | 36 | transformed_dict[key] = val 37 | 38 | return transformed_dict 39 | -------------------------------------------------------------------------------- /config/transforms/serialize.py: -------------------------------------------------------------------------------- 1 | from config.transforms.props import PropsTransform 2 | 3 | 4 | class SerializeTransform(PropsTransform): 5 | 6 | def __init__(self, serialize_map): 7 | """ 8 | { 9 | "keystore": serializer 10 | } 11 | :param serialize_map: 12 | :type serialize_map: 13 | """ 14 | transform_map = {} 15 | reverse_map = {} 16 | for key, val in serialize_map: 17 | transform_map[key] = lambda key, val: key, serialize_map[key].serialize(val) 18 | reverse_map[key] = lambda key, val: key, serialize_map[key].deserialize(val) 19 | 20 | super(SerializeTransform, self).__init__(transform_map=transform_map, reverse_map=reverse_map) 21 | 22 | -------------------------------------------------------------------------------- /config/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/v1/__init__.py -------------------------------------------------------------------------------- /config/v1/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/v1/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /config/v1/__pycache__/config.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/v1/__pycache__/config.cpython-38.pyc -------------------------------------------------------------------------------- /config/v1/__pycache__/serialize.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/config/v1/__pycache__/serialize.cpython-38.pyc -------------------------------------------------------------------------------- /config/v1/config.py: -------------------------------------------------------------------------------- 1 | from config.base import config 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class Config(config.Config): 8 | def __init__( 9 | self, 10 | phone=None, 11 | cc=None, 12 | login=None, 13 | password=None, 14 | pushname=None, 15 | id=None, 16 | mcc=None, 17 | mnc=None, 18 | sim_mcc=None, 19 | sim_mnc=None, 20 | client_static_keypair=None, 21 | server_static_public=None, 22 | expid=None, 23 | fdid=None, 24 | edge_routing_info=None, 25 | chat_dns_domain=None, 26 | backup_token=None, 27 | country=None, 28 | env_name=None, 29 | proxy=None 30 | 31 | ): 32 | super(Config, self).__init__(1) 33 | 34 | self._phone = str(phone) if phone is not None else None # type: str 35 | self._cc = cc # type: int 36 | self._login = str(login) if login is not None else None # type: str 37 | self._password = password # type: str 38 | self._pushname = pushname # type: str 39 | self._id = id 40 | self._client_static_keypair = client_static_keypair 41 | self._server_static_public = server_static_public 42 | self._expid = expid 43 | self._fdid = fdid 44 | self._mcc = mcc 45 | self._mnc = mnc 46 | self._sim_mcc = sim_mcc 47 | self._sim_mnc = sim_mnc 48 | self._edge_routing_info = edge_routing_info 49 | self._chat_dns_domain = chat_dns_domain 50 | self._backup_token = backup_token 51 | self._country = country 52 | self._env_name = env_name 53 | self._proxy = proxy 54 | 55 | 56 | if self._password is not None: 57 | logger.warn("Setting a password in Config is deprecated and not used anymore. " 58 | "client_static_keypair is used instead") 59 | 60 | def __str__(self): 61 | from config.v1.serialize import ConfigSerialize 62 | from config.transforms.dict_json import DictJsonTransform 63 | return DictJsonTransform().transform(ConfigSerialize(self.__class__).serialize(self)) 64 | 65 | @property 66 | def phone(self): 67 | return self._phone 68 | 69 | @phone.setter 70 | def phone(self, value): 71 | self._phone = str(value) if value is not None else None 72 | 73 | @property 74 | def cc(self): 75 | return self._cc 76 | 77 | @cc.setter 78 | def cc(self, value): 79 | self._cc = value 80 | 81 | @property 82 | def login(self): 83 | return self._login 84 | 85 | @login.setter 86 | def login(self, value): 87 | self._login= value 88 | 89 | @property 90 | def password(self): 91 | return self._password 92 | 93 | @password.setter 94 | def password(self, value): 95 | self._password = value 96 | if value is not None: 97 | logger.warn("Setting a password in Config is deprecated and not used anymore. " 98 | "client_static_keypair is used instead") 99 | 100 | @property 101 | def pushname(self): 102 | return self._pushname 103 | 104 | @pushname.setter 105 | def pushname(self, value): 106 | self._pushname = value 107 | 108 | @property 109 | def mcc(self): 110 | return self._mcc 111 | 112 | @mcc.setter 113 | def mcc(self, value): 114 | self._mcc = value 115 | 116 | @property 117 | def mnc(self): 118 | return self._mnc 119 | 120 | @mnc.setter 121 | def mnc(self, value): 122 | self._mnc = value 123 | 124 | @property 125 | def sim_mcc(self): 126 | return self._sim_mcc 127 | 128 | @sim_mcc.setter 129 | def sim_mcc(self, value): 130 | self._sim_mcc = value 131 | 132 | @property 133 | def sim_mnc(self): 134 | return self._sim_mnc 135 | 136 | @sim_mnc.setter 137 | def sim_mnc(self, value): 138 | self._sim_mnc = value 139 | 140 | @property 141 | def id(self): 142 | return self._id 143 | 144 | @id.setter 145 | def id(self, value): 146 | self._id = value 147 | 148 | @property 149 | def client_static_keypair(self): 150 | return self._client_static_keypair 151 | 152 | @client_static_keypair.setter 153 | def client_static_keypair(self, value): 154 | self._client_static_keypair = value 155 | 156 | @property 157 | def server_static_public(self): 158 | return self._server_static_public 159 | 160 | @server_static_public.setter 161 | def server_static_public(self, value): 162 | self._server_static_public = value 163 | 164 | @property 165 | def expid(self): 166 | return self._expid 167 | 168 | @expid.setter 169 | def expid(self, value): 170 | self._expid = value 171 | 172 | @property 173 | def fdid(self): 174 | return self._fdid 175 | 176 | @fdid.setter 177 | def fdid(self, value): 178 | self._fdid = value 179 | 180 | @property 181 | def edge_routing_info(self): 182 | return self._edge_routing_info 183 | 184 | @edge_routing_info.setter 185 | def edge_routing_info(self, value): 186 | self._edge_routing_info = value 187 | 188 | @property 189 | def chat_dns_domain(self): 190 | return self._chat_dns_domain 191 | 192 | @chat_dns_domain.setter 193 | def chat_dns_domain(self, value): 194 | self._chat_dns_domain = value 195 | 196 | @property 197 | def backup_token(self): 198 | return self._backup_token 199 | 200 | @backup_token.setter 201 | def backup_token(self, value): 202 | self._backup_token = value 203 | 204 | @property 205 | def country(self): 206 | if self._country: 207 | return self._country 208 | else: 209 | return "id" 210 | 211 | @country.setter 212 | def country(self, value): 213 | self._country = str(value) 214 | 215 | @property 216 | def env_name(self): 217 | if self._env_name: 218 | return self._env_name 219 | else: 220 | return "ios" 221 | 222 | @env_name.setter 223 | def env_name(self, value): 224 | self._env_name = str(value) 225 | 226 | @property 227 | def proxy(self): 228 | if self._proxy: 229 | return self._proxy 230 | else: 231 | return "" 232 | 233 | @proxy.setter 234 | def proxy(self, value): 235 | self._proxy = str(value) 236 | -------------------------------------------------------------------------------- /config/v1/serialize.py: -------------------------------------------------------------------------------- 1 | from config.base import serialize 2 | from config.transforms.filter import FilterTransform 3 | from config.transforms.meta import MetaPropsTransform 4 | from config.transforms.map import MapTransform 5 | from config.transforms.config_dict import ConfigDictTransform 6 | from config.transforms.props import PropsTransform 7 | 8 | from consonance.structs.keypair import KeyPair 9 | from consonance.structs.publickey import PublicKey 10 | import base64 11 | 12 | 13 | class ConfigSerialize(serialize.ConfigSerialize): 14 | def __init__(self, config_class): 15 | super(ConfigSerialize, self).__init__( 16 | transforms=( 17 | ConfigDictTransform(config_class), 18 | FilterTransform( 19 | transform_filter=lambda key, val: val is not None, 20 | reverse_filter=lambda key, val: key != "version" 21 | ), 22 | MapTransform(transform_map=lambda key, val: (key[1:], val)), 23 | PropsTransform( 24 | transform_map={ 25 | "server_static_public": lambda key, val: (key, base64.b64encode(val.data).decode()), 26 | "client_static_keypair": lambda key, val: (key, base64.b64encode(val.private.data + val.public.data).decode()), 27 | "id": lambda key, val: (key, base64.b64encode(val).decode()), 28 | "backup_token": lambda key, val: (key, base64.b64encode(val).decode()), 29 | "expid": lambda key, val: (key, base64.b64encode(val).decode()), 30 | "edge_routing_info": lambda key, val: (key, base64.b64encode(val).decode()) 31 | }, 32 | reverse_map={ 33 | "server_static_public": lambda key, val: (key, PublicKey(base64.b64decode(val))), 34 | "client_static_keypair": lambda key, val: (key, KeyPair.from_bytes(base64.b64decode(val))), 35 | "id": lambda key, val: (key, base64.b64decode(val)), 36 | "backup_token": lambda key, val: (key, base64.b64decode(val)), 37 | "expid": lambda key, val: (key, base64.b64decode(val)), 38 | "edge_routing_info": lambda key, val: (key, base64.b64decode(val)) 39 | } 40 | ), 41 | MetaPropsTransform(meta_props=("version", )), 42 | ) 43 | ) 44 | -------------------------------------------------------------------------------- /env/__init__.py: -------------------------------------------------------------------------------- 1 | from .env import MyEnv 2 | from .env_ios import iOSMyEnv 3 | from .env_android import AndroidMyEnv 4 | 5 | from .env_config import MyEnvConfig 6 | from .env_config_id import IDEnvConfig 7 | from .env_config_kz import KZEnvConfig 8 | from .env_config_ru import RUEnvConfig 9 | from .env_config_kh import KHEnvConfig 10 | from .env_config_ph import PHEnvConfig 11 | from .env_config_hk import HKEnvConfig 12 | from .env_config_vn import VNEnvConfig 13 | from .env_config_us import USEnvConfig 14 | 15 | 16 | import logging 17 | 18 | logger = logging.getLogger(__name__) 19 | ch = logging.StreamHandler() 20 | # ch.setLevel(logging.DEBUG) 21 | 22 | formatter = logging.Formatter('%(levelname).1s %(asctime)s %(name)s - %(message)s') 23 | 24 | ch.setFormatter(formatter) 25 | 26 | logger.addHandler(ch) 27 | -------------------------------------------------------------------------------- /env/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_app.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_app.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config_id.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config_id.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config_kh.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config_kh.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config_kz.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config_kz.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config_ph.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config_ph.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_config_ru.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_config_ru.cpython-38.pyc -------------------------------------------------------------------------------- /env/__pycache__/env_ios.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/env/__pycache__/env_ios.cpython-38.pyc -------------------------------------------------------------------------------- /env/env.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import logging 3 | from six import with_metaclass 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | DEFAULT = "ios" 8 | 9 | 10 | class MyEnvType(abc.ABCMeta): 11 | def __init__(cls, name, bases, dct): 12 | if name != "MyEnv": 13 | MyEnv.registerEnv(cls) 14 | super(MyEnvType, cls).__init__(name, bases, dct) 15 | 16 | 17 | class MyEnv(with_metaclass(MyEnvType, object)): 18 | __metaclass__ = MyEnvType 19 | __ENVS = {} 20 | __CURR = None 21 | # WhatsApp/2.20.102 iOS/12.4.8 Device/iPhone_7Z 22 | _USERAGENT_STRING = "WhatsApp/{WHATSAPP_VERSION} {OS_NAME}/{OS_VERSION} Device/{DEVICE_NAME}_{DEVICE_TYPE}" 23 | 24 | @classmethod 25 | def registerEnv(cls, envCls): 26 | envName = envCls.__name__.lower().replace("myenv", "") 27 | cls.__ENVS[envName] = envCls 28 | logger.debug("registered env %s => %s" % (envName, envCls)) 29 | 30 | @classmethod 31 | def setEnv(cls, envName): 32 | if not envName in cls.__ENVS: 33 | raise ValueError("%s env does not exist" % envName) 34 | logger.debug("Current env changed to %s " % envName) 35 | cls.__CURR = cls.__ENVS[envName]() 36 | 37 | @classmethod 38 | def getEnv(cls, envName): 39 | if not envName in cls.__ENVS: 40 | raise ValueError("%s env does not exist" % envName) 41 | 42 | return cls.__ENVS[envName]() 43 | 44 | @classmethod 45 | def getRegisteredEnvs(cls): 46 | return list(cls.__ENVS.keys()) 47 | 48 | @classmethod 49 | def getCurrent(cls): 50 | """ 51 | :rtype: iOSEnv 52 | """ 53 | if cls.__CURR is None: 54 | env = DEFAULT 55 | envs = cls.getRegisteredEnvs() 56 | if env not in envs: 57 | env = envs[0] 58 | logger.debug("Env not set, setting it to %s" % env) 59 | cls.setEnv(env) 60 | return cls.__CURR 61 | 62 | @abc.abstractmethod 63 | def getToken(self, phoneNumber): 64 | pass 65 | 66 | @abc.abstractmethod 67 | def getVersion(self): 68 | pass 69 | 70 | @abc.abstractmethod 71 | def getOSVersion(self): 72 | pass 73 | 74 | @abc.abstractmethod 75 | def getOSName(self): 76 | pass 77 | 78 | @abc.abstractmethod 79 | def getDeviceName(self): 80 | pass 81 | 82 | @abc.abstractmethod 83 | def getDeviceModel(self): 84 | pass 85 | 86 | @abc.abstractmethod 87 | def getDeviceType(self): 88 | pass 89 | 90 | @abc.abstractmethod 91 | def getManufacturer(self): 92 | pass 93 | 94 | def getBuildVersion(self): 95 | pass 96 | 97 | def getUserAgent(self): 98 | return self.__class__._USERAGENT_STRING.format( 99 | WHATSAPP_VERSION=self.getVersion(), 100 | OS_NAME=self.getOSName(), 101 | OS_VERSION=self.getOSVersion(), 102 | DEVICE_NAME=self.getDeviceName(), 103 | DEVICE_TYPE=self.getDeviceType() 104 | 105 | ) 106 | -------------------------------------------------------------------------------- /env/env_android.py: -------------------------------------------------------------------------------- 1 | from .env import MyEnv 2 | import base64 3 | import hashlib 4 | 5 | 6 | class AndroidMyEnv(MyEnv): 7 | _SIGNATURE = "MIIDMjCCAvCgAwIBAgIETCU2pDALBgcqhkjOOAQDBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNV" \ 8 | "BAcTC1NhbnRhIENsYXJhMRYwFAYDVQQKEw1XaGF0c0FwcCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEUMBIGA1UEAxMLQnJ" \ 9 | "pYW4gQWN0b24wHhcNMTAwNjI1MjMwNzE2WhcNNDQwMjE1MjMwNzE2WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5" \ 10 | "pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExFjAUBgNVBAoTDVdoYXRzQXBwIEluYy4xFDASBgNVBAsTC0VuZ2luZWVyaW5nMRQwEg" \ 11 | "YDVQQDEwtCcmlhbiBBY3RvbjCCAbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEm" \ 12 | "aUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCN" \ 13 | "VQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jr" \ 14 | "qgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO" \ 15 | "8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDRGYtLgWh7zyRtQainJfCpiaUbzjJuhMgo4fVWZIvXHaS" \ 16 | "HBU1t5w//S0lDK2hiqkj8KpMWGywVov9eZxZy37V26dEqr/c2m5qZ0E+ynSu7sqUD7kGx/zeIcGT0H+KAVgkGNQCo5Uc0koLRW" \ 17 | "YHNtYoIvt5R3X6YZylbPftF/8ayWTALBgcqhkjOOAQDBQADLwAwLAIUAKYCp0d6z4QQdyN74JDfQ2WCyi8CFDUM4CaNB+ceVXd" \ 18 | "KtOrNTQcc0e+t" 19 | 20 | _MD5_CLASSES = "WuFH18yXKRVezywQm+S24A==" 21 | _KEY = "eQV5aq/Cg63Gsq1sshN9T3gh+UUp0wIw0xgHYT1bnCjEqOJQKCRrWxdAe2yvsDeCJL+Y4G3PRD2HUF7oUgiGo8vGlNJOaux26k+A2F3hj8A=" 22 | 23 | _VERSION = "2.21.21.18" # 2.20.206.24 24 | _OS_NAME = "Android" 25 | _OS_VERSION = "8.0.0" 26 | _DEVICE_NAME = "star2lte" 27 | _MANUFACTURER = "samsung" 28 | _BUILD_VERSION = "star2ltexx-user 8.0.0 R16NW G965FXXU1ARCC release-keys" 29 | _AXOLOTL = True 30 | 31 | def getVersion(self): 32 | return self.__class__._VERSION 33 | 34 | def getOSName(self): 35 | return self.__class__._OS_NAME 36 | 37 | def getOSVersion(self): 38 | return self.__class__._OS_VERSION 39 | 40 | def getDeviceName(self): 41 | return self.__class__._DEVICE_NAME 42 | 43 | def getBuildVersion(self): 44 | return self.__class__._BUILD_VERSION 45 | 46 | def getManufacturer(self): 47 | return self.__class__._MANUFACTURER 48 | 49 | def isAxolotlEnabled(self): 50 | return self.__class__._AXOLOTL 51 | 52 | def getDeviceType(self): 53 | return "" 54 | 55 | def getToken(self, phoneNumber): 56 | keyDecoded = bytearray(base64.b64decode(self.__class__._KEY)) 57 | sigDecoded = base64.b64decode(self.__class__._SIGNATURE) 58 | clsDecoded = base64.b64decode(self.__class__._MD5_CLASSES) 59 | data = sigDecoded + clsDecoded + phoneNumber.encode() 60 | 61 | opad = bytearray() 62 | ipad = bytearray() 63 | for i in range(0, 64): 64 | opad.append(0x5C ^ keyDecoded[i]) 65 | ipad.append(0x36 ^ keyDecoded[i]) 66 | hash = hashlib.sha1() 67 | subHash = hashlib.sha1() 68 | try: 69 | subHash.update(ipad + data) 70 | hash.update(opad + subHash.digest()) 71 | except TypeError: 72 | subHash.update(bytes(ipad + data)) 73 | hash.update(bytes(opad + subHash.digest())) 74 | result = base64.b64encode(hash.digest()) 75 | return result 76 | -------------------------------------------------------------------------------- /env/env_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | APP_DATA = { 4 | "gc.whatsappprotocolregisterapi": 1, 5 | "gc.whatsappprotocolexportgroup": 2, 6 | "gc.playapi": 3, 7 | "gc.groupmessagecatchapi": 4 8 | } 9 | 10 | 11 | def app_env(): 12 | app = os.getenv('APP') 13 | app_type = APP_DATA.get(app) 14 | return app_type 15 | -------------------------------------------------------------------------------- /env/env_config.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import logging 3 | from six import with_metaclass 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | # 印度尼西亚 8 | DEFAULT = "ID" 9 | 10 | 11 | class MyEnvConfigType(abc.ABCMeta): 12 | def __init__(cls, name, bases, dct): 13 | if name != "MyEnvConfig": 14 | MyEnvConfig.registerEnv(cls) 15 | super(MyEnvConfigType, cls).__init__(name, bases, dct) 16 | 17 | 18 | class MyEnvConfig(with_metaclass(MyEnvConfigType, object)): 19 | __metaclass__ = MyEnvConfigType 20 | _PROXY_NAME = "kkone88-zone-custom-region-{country}-session-{session_name}-sessTime-{session_time}" 21 | 22 | __ENVS = {} 23 | __CURR = None 24 | 25 | @classmethod 26 | def registerEnv(cls, envCls): 27 | envName = envCls.__name__.lower().replace("envconfig", "") 28 | cls.__ENVS[envName] = envCls 29 | logger.debug("registered envconfig %s => %s" % (envName, envCls)) 30 | 31 | @classmethod 32 | def setEnv(cls, envName): 33 | if not envName in cls.__ENVS: 34 | raise ValueError("%s envconfig does not exist" % envName) 35 | logger.debug("Current env changed to %s " % envName) 36 | cls.__CURR = cls.__ENVS[envName]() 37 | 38 | @classmethod 39 | def getEnv(cls, envName): 40 | if not envName in cls.__ENVS: 41 | raise ValueError("%s envconfig does not exist" % envName) 42 | 43 | return cls.__ENVS[envName]() 44 | 45 | @classmethod 46 | def getRegisteredEnvs(cls): 47 | return list(cls.__ENVS.keys()) 48 | 49 | @classmethod 50 | def getCurrent(cls): 51 | """ 52 | :rtype: 印度尼西亚 53 | """ 54 | if cls.__CURR is None: 55 | env = DEFAULT 56 | envs = cls.getRegisteredEnvs() 57 | if env not in envs: 58 | env = envs[0] 59 | logger.debug("EnvConfig not set, setting it to %s" % env) 60 | cls.setEnv(env) 61 | return cls.__CURR 62 | 63 | @abc.abstractmethod 64 | def getJinDou(self): 65 | pass 66 | 67 | @abc.abstractmethod 68 | def getCc(self): 69 | pass 70 | 71 | @abc.abstractmethod 72 | def getCountry(self): 73 | pass 74 | 75 | @abc.abstractmethod 76 | def getMcc(self): 77 | pass 78 | 79 | @abc.abstractmethod 80 | def getMnc(self): 81 | pass 82 | 83 | @abc.abstractmethod 84 | def getDuringTime(self): 85 | pass 86 | 87 | 88 | -------------------------------------------------------------------------------- /env/env_config_hk.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 香港 4 | class HKEnvConfig(MyEnvConfig): 5 | _JINDOU = "2800" 6 | _CC = "852" 7 | _COUNTRY = "hk" 8 | _MCC = "454" 9 | _MNC = "007" 10 | _DURING = 20 11 | 12 | 13 | def getJinDou(self): 14 | return self.__class__._JINDOU 15 | 16 | def getCc(self): 17 | return self.__class__._CC 18 | 19 | def getCountry(self): 20 | return self.__class__._COUNTRY 21 | 22 | def getMcc(self): 23 | return self.__class__._MCC 24 | 25 | def getMnc(self): 26 | return self.__class__._MNC 27 | 28 | def getDuringTime(self): 29 | return self.__class__._DURING 30 | 31 | def getProxyName(self, mobile): 32 | return self.__class__._PROXY_NAME.format( 33 | country=self.getCountry(), 34 | session_name=mobile, 35 | session_time=self.getDuringTime() 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /env/env_config_id.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 印度尼西亚 4 | class IDEnvConfig(MyEnvConfig): 5 | _JINDOU = "2800" 6 | _CC = "62" 7 | _COUNTRY = "id" 8 | _MCC = "510" 9 | _MNC = "001" 10 | _DURING = 20 11 | 12 | 13 | def getJinDou(self): 14 | return self.__class__._JINDOU 15 | 16 | def getCc(self): 17 | return self.__class__._CC 18 | 19 | def getCountry(self): 20 | return self.__class__._COUNTRY 21 | 22 | def getMcc(self): 23 | return self.__class__._MCC 24 | 25 | def getMnc(self): 26 | return self.__class__._MNC 27 | 28 | def getDuringTime(self): 29 | return self.__class__._DURING 30 | 31 | def getProxyName(self, mobile): 32 | return self.__class__._PROXY_NAME.format( 33 | country=self.getCountry(), 34 | session_name=mobile, 35 | session_time=self.getDuringTime() 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /env/env_config_kh.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 柬埔寨 4 | class KHEnvConfig(MyEnvConfig): 5 | _JINDOU = "2123" 6 | _CC = "855" 7 | _COUNTRY = "kh" 8 | _MCC = "456" 9 | # 02 04 06 08 09 18 10 | _MNC = "02" 11 | _DURING = 20 12 | 13 | 14 | def getJinDou(self): 15 | return self.__class__._JINDOU 16 | 17 | def getCc(self): 18 | return self.__class__._CC 19 | 20 | def getCountry(self): 21 | return self.__class__._COUNTRY 22 | 23 | def getMcc(self): 24 | return self.__class__._MCC 25 | 26 | def getMnc(self): 27 | return self.__class__._MNC 28 | 29 | def getDuringTime(self): 30 | return self.__class__._DURING 31 | 32 | def getProxyName(self, mobile): 33 | return self.__class__._PROXY_NAME.format( 34 | country=self.getCountry(), 35 | session_name=mobile, 36 | session_time=self.getDuringTime() 37 | 38 | ) 39 | -------------------------------------------------------------------------------- /env/env_config_kz.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 哈萨克斯坦 4 | class KZEnvConfig(MyEnvConfig): 5 | _JINDOU = "2839" 6 | _CC = "7" 7 | _COUNTRY = "kz" 8 | _MCC = "401" 9 | # 01 02 08 77 10 | _MNC = "001" 11 | _DURING = 20 12 | 13 | 14 | def getJinDou(self): 15 | return self.__class__._JINDOU 16 | 17 | def getCc(self): 18 | return self.__class__._CC 19 | 20 | def getCountry(self): 21 | return self.__class__._COUNTRY 22 | 23 | def getMcc(self): 24 | return self.__class__._MCC 25 | 26 | def getMnc(self): 27 | return self.__class__._MNC 28 | 29 | def getDuringTime(self): 30 | return self.__class__._DURING 31 | 32 | def getProxyName(self, mobile): 33 | return self.__class__._PROXY_NAME.format( 34 | country=self.getCountry(), 35 | session_name=mobile, 36 | session_time=self.getDuringTime() 37 | 38 | ) 39 | -------------------------------------------------------------------------------- /env/env_config_ph.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 菲律宾 4 | class PHEnvConfig(MyEnvConfig): 5 | _JINDOU = "2981" 6 | _CC = "63" 7 | _COUNTRY = "ph" 8 | _MCC = "515" 9 | _MNC = "002" 10 | _DURING = 20 11 | 12 | 13 | def getJinDou(self): 14 | return self.__class__._JINDOU 15 | 16 | def getCc(self): 17 | return self.__class__._CC 18 | 19 | def getCountry(self): 20 | return self.__class__._COUNTRY 21 | 22 | def getMcc(self): 23 | return self.__class__._MCC 24 | 25 | def getMnc(self): 26 | return self.__class__._MNC 27 | 28 | def getDuringTime(self): 29 | return self.__class__._DURING 30 | 31 | def getProxyName(self, mobile): 32 | return self.__class__._PROXY_NAME.format( 33 | country=self.getCountry(), 34 | session_name=mobile, 35 | session_time=self.getDuringTime() 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /env/env_config_ru.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 俄罗斯 4 | class RUEnvConfig(MyEnvConfig): 5 | _JINDOU = "2838" 6 | _CC = "7" 7 | _COUNTRY = "ru" 8 | _MCC = "250" 9 | _MNC = "002" 10 | _DURING = 20 11 | 12 | 13 | def getJinDou(self): 14 | return self.__class__._JINDOU 15 | 16 | def getCc(self): 17 | return self.__class__._CC 18 | 19 | def getCountry(self): 20 | return self.__class__._COUNTRY 21 | 22 | def getMcc(self): 23 | return self.__class__._MCC 24 | 25 | def getMnc(self): 26 | return self.__class__._MNC 27 | 28 | def getDuringTime(self): 29 | return self.__class__._DURING 30 | 31 | def getProxyName(self, mobile): 32 | return self.__class__._PROXY_NAME.format( 33 | country=self.getCountry(), 34 | session_name=mobile, 35 | session_time=self.getDuringTime() 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /env/env_config_us.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 美国 4 | class USEnvConfig(MyEnvConfig): 5 | _CC = "1" 6 | _COUNTRY = "u's" 7 | _MCC = "310" 8 | _MNC = "032" 9 | _DURING = 20 10 | 11 | 12 | def getJinDou(self): 13 | return self.__class__._JINDOU 14 | 15 | def getCc(self): 16 | return self.__class__._CC 17 | 18 | def getCountry(self): 19 | return self.__class__._COUNTRY 20 | 21 | def getMcc(self): 22 | return self.__class__._MCC 23 | 24 | def getMnc(self): 25 | return self.__class__._MNC 26 | 27 | def getDuringTime(self): 28 | return self.__class__._DURING 29 | 30 | def getProxyName(self, mobile): 31 | return self.__class__._PROXY_NAME.format( 32 | country=self.getCountry(), 33 | session_name=mobile, 34 | session_time=self.getDuringTime() 35 | 36 | ) 37 | -------------------------------------------------------------------------------- /env/env_config_vn.py: -------------------------------------------------------------------------------- 1 | from .env_config import MyEnvConfig 2 | 3 | # 越南 4 | class VNEnvConfig(MyEnvConfig): 5 | _JINDOU = "2838" 6 | _CC = "84" 7 | _COUNTRY = "vn" 8 | _MCC = "452" 9 | _MNC = "002" 10 | _DURING = 20 11 | 12 | 13 | def getJinDou(self): 14 | return self.__class__._JINDOU 15 | 16 | def getCc(self): 17 | return self.__class__._CC 18 | 19 | def getCountry(self): 20 | return self.__class__._COUNTRY 21 | 22 | def getMcc(self): 23 | return self.__class__._MCC 24 | 25 | def getMnc(self): 26 | return self.__class__._MNC 27 | 28 | def getDuringTime(self): 29 | return self.__class__._DURING 30 | 31 | def getProxyName(self, mobile): 32 | return self.__class__._PROXY_NAME.format( 33 | country=self.getCountry(), 34 | session_name=mobile, 35 | session_time=self.getDuringTime() 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /examples/walogin_handshake_ik.py: -------------------------------------------------------------------------------- 1 | from consonance.structs.keypair import KeyPair 2 | from consonance.structs.publickey import PublicKey 3 | from consonance.protocol import WANoiseProtocol 4 | from consonance.config.client import ClientConfig 5 | from consonance.streams.segmented.wa import WASegmentedStream 6 | from consonance.streams.arbitrary.arbitrary_socket import SocketArbitraryStream 7 | from consonance.config.templates.useragent_iPhone import iPhoneUserAgentConfig 8 | import consonance 9 | import uuid 10 | import dissononce 11 | import socket 12 | import logging 13 | import sys 14 | import base64 15 | from axolotl.ecc.curve import Curve 16 | 17 | consonance.logger.setLevel(logging.DEBUG) 18 | dissononce.logger.setLevel(logging.DEBUG) 19 | 20 | # username is phone number 21 | USERNAME = 77472412416 22 | # on Android fetch client_static_keypair from /data/data/com.whatsapp/shared_prefs/keystore.xml 23 | KEYPAIR = KeyPair.from_bytes( 24 | base64.b64decode(b"8Ccclxofr7K1KIY2Y2DUjzyTRmsUM1z1jXj9Nzx9mVRtf3toEZBMgkmCMq4gKWVdwftl3DULtMyO15YfIbJOAA==") 25 | ) 26 | 27 | ENC_PUBKEY = Curve.decodePoint( 28 | bytearray([ 29 | 5, 142, 140, 15, 116, 195, 235, 197, 215, 166, 134, 92, 108, 30 | 60, 132, 56, 86, 176, 97, 33, 204, 232, 234, 119, 77, 34, 251, 31 | 111, 18, 37, 18, 48, 45 32 | ]) 33 | ) 34 | 35 | WA_PUBLIC = PublicKey(ENC_PUBKEY.publicKey) 36 | # same phone_id/fdid used at registration. 37 | # on Android it's phoneid_id under /data/data/com.whatsapp/shared_prefs/com.whatsapp_preferences.xml 38 | PHONE_ID = uuid.uuid4().__str__() 39 | # create full configuration which will translate later into a protobuf payload 40 | CONFIG = ClientConfig( 41 | username=USERNAME, 42 | passive=True, 43 | useragent=iPhoneUserAgentConfig( 44 | app_version="2.20.102", 45 | phone_id=PHONE_ID 46 | ), 47 | pushname="consonance", 48 | short_connect=True 49 | ) 50 | ENDPOINT = ("g.whatsapp.net", 443) 51 | HEADER = b"WA\x04\x01" 52 | 53 | if __name__ == "__main__": 54 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 55 | s.connect(ENDPOINT) 56 | # send WA header indicating protocol version 57 | s.send(HEADER) 58 | # use WASegmentedStream for sending/receiving in frames 59 | stream = WASegmentedStream(SocketArbitraryStream(s)) 60 | # initialize WANoiseProtocol 2.1 61 | wa_noiseprotocol = WANoiseProtocol(4, 1) 62 | # start the protocol, this should attempt a IK handshake since we 63 | # specifying the remote static public key. The handshake should 64 | # succeeds if the WA_PUBLIC is valid, but authentication should fail. 65 | if wa_noiseprotocol.start(stream, CONFIG, KEYPAIR, rs=WA_PUBLIC): 66 | print("Handshake completed, checking authentication...") 67 | # we are now in transport phase, first incoming data 68 | # will indicate whether we are authenticated 69 | first_transport_data = wa_noiseprotocol.receive() 70 | # fourth byte is status, 172 is success, 52 is failure 71 | if first_transport_data[3] == 172: 72 | print("Authentication succeeded") 73 | elif first_transport_data[3] == 52: 74 | print("Authentication failed") 75 | sys.exit(1) 76 | else: 77 | print("Unrecognized authentication response: %s" % (first_transport_data[3])) 78 | sys.exit(1) 79 | else: 80 | print("Handshake failed") 81 | sys.exit(1) 82 | -------------------------------------------------------------------------------- /examples/walogin_handshake_xx.py: -------------------------------------------------------------------------------- 1 | from consonance.structs.keypair import KeyPair 2 | from consonance.protocol import WANoiseProtocol 3 | from consonance.config.client import ClientConfig 4 | from consonance.streams.segmented.wa import WASegmentedStream 5 | from consonance.streams.arbitrary.arbitrary_socket import SocketArbitraryStream 6 | from consonance.config.templates.useragent_iPhone import iPhoneUserAgentConfig 7 | import consonance 8 | import uuid 9 | import dissononce 10 | import socket 11 | import logging 12 | import sys 13 | import base64 14 | 15 | consonance.logger.setLevel(logging.DEBUG) 16 | dissononce.logger.setLevel(logging.DEBUG) 17 | 18 | # username is phone number 19 | USERNAME = 6283890513211 20 | # on Android fetch client_static_keypair from /data/data/com.whatsapp/shared_prefs/keystore.xml 21 | KEYPAIR = KeyPair.from_bytes( 22 | base64.b64decode(b"kDZxVPzYhrNKHA2YczenBX495Lfk5eEPNdRV98Eq5Ut1EMsXJP7I1U0jBwmIwchXylf7SdVV25hDLwmr1JgWVQ==") 23 | ) 24 | # same phone_id/fdid used at registration. 25 | # on Android it's phoneid_id under /data/data/com.whatsapp/shared_prefs/com.whatsapp_preferences.xml 26 | PHONE_ID = '55a6dced-cfb6-4851-b816-18ab591f1e8a' 27 | # create full configuration which will translate later into a protobuf payload 28 | CONFIG = ClientConfig( 29 | username=USERNAME, 30 | passive=True, 31 | useragent=iPhoneUserAgentConfig( 32 | app_version="2.22.16.77", 33 | phone_id=PHONE_ID 34 | ), 35 | pushname="wer u", 36 | short_connect=True 37 | ) 38 | PROTOCOL_VERSION = (5, 2) 39 | ENDPOINT = ("e1.whatsapp.net", 443) 40 | HEADER = b"WA" + bytes(PROTOCOL_VERSION) 41 | 42 | if __name__ == "__main__": 43 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 44 | s.connect(ENDPOINT) 45 | # send WA header indicating protocol version 46 | s.send(HEADER) 47 | # use WASegmentedStream for sending/receiving in frames 48 | stream = WASegmentedStream(SocketArbitraryStream(s)) 49 | # initialize WANoiseProtocol 50 | wa_noiseprotocol = WANoiseProtocol(*PROTOCOL_VERSION) 51 | # start the protocol, this should a XX handshake since 52 | # we are not passing the remote static public key 53 | try: 54 | wa_noiseprotocol.start(stream, CONFIG, KEYPAIR) 55 | print("Handshake completed, checking authentication...") 56 | # we are now in transport phase, first incoming data 57 | # will indicate whether we are authenticated 58 | first_transport_data = wa_noiseprotocol.receive() 59 | # fourth + fifth byte are status, [237, 38] is failure 60 | if first_transport_data[3] == 51: 61 | print("Authentication succeeded") 62 | elif list(first_transport_data[3:5]) == [237, 38]: 63 | print("Authentication failed") 64 | sys.exit(1) 65 | else: 66 | print("Unrecognized authentication response: %s" % (first_transport_data[3])) 67 | sys.exit(1) 68 | except: 69 | print("Handshake failed") 70 | sys.exit(1) 71 | -------------------------------------------------------------------------------- /examples/walogin_handshake_xxfallback.py: -------------------------------------------------------------------------------- 1 | from consonance.structs.keypair import KeyPair 2 | from consonance.protocol import WANoiseProtocol 3 | from consonance.config.client import ClientConfig 4 | from consonance.streams.segmented.wa import WASegmentedStream 5 | from consonance.streams.arbitrary.arbitrary_socket import SocketArbitraryStream 6 | from consonance.config.templates.useragent_iPhone import iPhoneUserAgentConfig 7 | import consonance 8 | import uuid 9 | import dissononce 10 | import socket 11 | import logging 12 | import sys 13 | import base64 14 | 15 | consonance.logger.setLevel(logging.DEBUG) 16 | dissononce.logger.setLevel(logging.DEBUG) 17 | 18 | # username is phone number 19 | USERNAME = 123456789 20 | # on Android fetch client_static_keypair from /data/data/com.whatsapp/shared_prefs/keystore.xml 21 | KEYPAIR = KeyPair.from_bytes( 22 | base64.b64decode(b"YJa8Vd9pG0KV2tDYi5V+DMOtSvCEFzRGCzOlGZkvBHzJvBE5C3oC2Fruniw0GBGo7HHgR4TjvjI3C9AihStsVg==") 23 | ) 24 | # using a random remote public key 25 | WA_PUBLIC = KeyPair.generate().public 26 | # same phone_id/fdid used at registration. 27 | # on Android it's phoneid_id under /data/data/com.whatsapp/shared_prefs/com.whatsapp_preferences.xml 28 | PHONE_ID = uuid.uuid4().__str__() 29 | # create full configuration which will translate later into a protobuf payload 30 | CONFIG = ClientConfig( 31 | username=USERNAME, 32 | passive=True, 33 | useragent=iPhoneUserAgentConfig( 34 | app_version="2.19.51", 35 | phone_id=PHONE_ID 36 | ), 37 | pushname="consonance", 38 | short_connect=True 39 | ) 40 | ENDPOINT = ("e1.whatsapp.net", 443) 41 | HEADER = b"WA\x02\x01" 42 | 43 | if __name__ == "__main__": 44 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 45 | s.connect(ENDPOINT) 46 | # send WA header indicating protocol version 47 | s.send(HEADER) 48 | # use WASegmentedStream for sending/receiving in frames 49 | stream = WASegmentedStream(SocketArbitraryStream(s)) 50 | # initialize WANoiseProtocol 2.1 51 | wa_noiseprotocol = WANoiseProtocol(2, 1) 52 | # start the protocol, this should attempt a IK handshake since we 53 | # specifying the remote static public key. The handshake should 54 | # fail because WA_PUBLIC is invalid. This results in reverting to 55 | # a xxfallack handshake. Note that eventually authentication 56 | # should fail anyways due to invalid credentials of this examples. 57 | if wa_noiseprotocol.start(stream, CONFIG, KEYPAIR, rs=WA_PUBLIC): 58 | print("Handshake completed, checking authentication...") 59 | # we are now in transport phase, first incoming data 60 | # will indicate whether we are authenticated 61 | first_transport_data = wa_noiseprotocol.receive() 62 | # fourth byte is status, 172 is success, 52 is failure 63 | if first_transport_data[3] == 172: 64 | print("Authentication succeeded") 65 | elif first_transport_data[3] == 52: 66 | print("Authentication failed") 67 | sys.exit(1) 68 | else: 69 | print("Unrecognized authentication response: %s" % (first_transport_data[3])) 70 | sys.exit(1) 71 | else: 72 | print("Handshake failed") 73 | sys.exit(1) 74 | -------------------------------------------------------------------------------- /login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/login.png -------------------------------------------------------------------------------- /messagestack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/messagestack/__init__.py -------------------------------------------------------------------------------- /messagestack/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/messagestack/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /messagestack/__pycache__/group_info.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/messagestack/__pycache__/group_info.cpython-38.pyc -------------------------------------------------------------------------------- /messagestack/__pycache__/play_group.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/messagestack/__pycache__/play_group.cpython-38.pyc -------------------------------------------------------------------------------- /messagestack/__pycache__/register.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/messagestack/__pycache__/register.cpython-38.pyc -------------------------------------------------------------------------------- /messagestack/group_info.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from common.singleonConfig import SingleonConfig 4 | from config.enumExec import ExecTypes 5 | from mycli.cli.exportgroup import ExportGroupUpload 6 | from mycli.http_request import requests_send 7 | from mycli.mysql.mysql_cli import mobile_update 8 | 9 | class ExportGroupResult(): 10 | 11 | @classmethod 12 | def fail(cls, result, type_num): 13 | 14 | mobile = result.get("phone") 15 | reason = result.get("reason") 16 | data = result.get("data") 17 | 18 | dataPenetrate = SingleonConfig().getParamsAction() 19 | upload_url = dataPenetrate.get("UploadUrl") 20 | work_upload_url = dataPenetrate.get("WorkUploadUrl") 21 | 22 | upload_data = { 23 | "FullPhoneNumber": mobile, 24 | "OptType": None, 25 | "OptAppType": 2, 26 | "OptTime": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f"), 27 | "OptIp": "", 28 | "OptReamrk": None, 29 | "ProtocolType": 1 30 | } 31 | if type_num == ExecTypes.login: 32 | 33 | if reason == 403: 34 | mobile_update(mobile, reason) 35 | upload_data["OptType"] = 5 36 | message = "封号" 37 | upload_data["OptReamrk"] = message 38 | else: 39 | upload_data["OptType"] = 2 40 | message = "登录失败" 41 | upload_data["OptReamrk"] = message 42 | ExportGroupUpload.send_http("POST", work_upload_url, upload_data, mobile, message) 43 | 44 | elif type_num == ExecTypes.group_join: 45 | 46 | group_link = data.get("GroupUrl") 47 | data["groupUser"] = [] 48 | 49 | upload_data["OptType"] = 7 50 | message = "加群[{}]失败".format(group_link) 51 | upload_data["OptReamrk"] = message 52 | upload_response = requests_send("POST", upload_url, json=data) 53 | if not upload_response: 54 | requests_send("POST", upload_url, json=upload_data) 55 | 56 | ExportGroupUpload.send_http("POST", work_upload_url, upload_data, mobile, message) 57 | 58 | elif type_num == ExecTypes.group_info: 59 | 60 | group_link = data.get("GroupUrl") 61 | data["groupUser"] = [] 62 | 63 | upload_data["OptType"] = 7 64 | message = "导群[{}]失败".format(group_link) 65 | upload_data["OptReamrk"] = message 66 | upload_response = requests_send("POST", upload_url, json=data) 67 | if not upload_response: 68 | requests_send("POST", upload_url, json=upload_data) 69 | 70 | ExportGroupUpload.send_http("POST", work_upload_url, upload_data, mobile, message) 71 | 72 | @classmethod 73 | def success(cls, result, type_num): 74 | data = result.get("data") 75 | mobile = result.get("phone") 76 | 77 | dataPenetrate = SingleonConfig().getParamsAction() 78 | upload_url = dataPenetrate.get("UploadUrl") 79 | work_upload_url = dataPenetrate.get("WorkUploadUrl") 80 | 81 | upload_data = { 82 | "FullPhoneNumber": mobile, 83 | "OptType": None, 84 | "OptAppType": 2, 85 | "OptTime": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f"), 86 | "OptIp": "", 87 | "OptReamrk": None, 88 | "ProtocolType": 1 89 | } 90 | if type_num == ExecTypes.login: 91 | 92 | upload_data["OptType"] = 1 93 | message = "登录成功" 94 | upload_data["OptReamrk"] = message 95 | ExportGroupUpload.send_http("POST", work_upload_url, upload_data, mobile, message) 96 | 97 | elif type_num == ExecTypes.group_join: 98 | print("导群加群成功") 99 | 100 | elif type_num == ExecTypes.group_info: 101 | 102 | group_link = data.get("GroupUrl") 103 | 104 | upload_data["OptType"] = 6 105 | message = "导群[{}]成功".format(group_link) 106 | upload_data["OptReamrk"] = message 107 | 108 | upload_response = requests_send("POST", upload_url, json=data) 109 | if not upload_response: 110 | requests_send("POST", upload_url, json=upload_data) 111 | 112 | ExportGroupUpload.send_http("POST", work_upload_url, upload_data, mobile, message) 113 | 114 | -------------------------------------------------------------------------------- /messagestack/group_msg_catch.py: -------------------------------------------------------------------------------- 1 | from config.enumExec import ExecTypes 2 | from mycli.cli.playgroup import logger 3 | from mycli.mysql.group_msg_catch import group_msg_insert, busy_update 4 | from mycli.mysql.mysql_cli import mobile_update 5 | 6 | 7 | class GroupMessageCatchResult(): 8 | 9 | @classmethod 10 | def fail(cls, result, type_num): 11 | 12 | phone = result.get("phone") 13 | reason = result.get("reason") 14 | 15 | if type_num == ExecTypes.login: 16 | if reason == 403: 17 | mobile_update(phone, reason) 18 | logger.info("{}:群消息抓取号码登录失败:{}".format(phone, reason)) 19 | 20 | @classmethod 21 | def success(cls, result, type_num): 22 | 23 | phone = result.get("phone") 24 | data = result.get("data") 25 | 26 | if type_num == ExecTypes.login: 27 | busy_update(phone, 1) 28 | logger.info("{}:群消息抓取号码登录成功".format(phone)) 29 | elif type_num == ExecTypes.receive_text: 30 | group_id = data.get("groupId") 31 | if group_id: 32 | message = data.get("text") 33 | message_id = data.get("messageId") 34 | group_msg_insert(phone, group_id, message, message_id) 35 | logger.info("{}:群消息入库成功:{}".format(phone, data)) 36 | -------------------------------------------------------------------------------- /messagestack/play_group.py: -------------------------------------------------------------------------------- 1 | from common.singleonConfig import SingleonConfig 2 | from config.enumExec import ExecTypes 3 | from mycli.cli.playgroup import PlayGroupHandle 4 | from mycli.logger import logger 5 | from mycli.mysql.mysql_cli import mobile_update 6 | 7 | class PlayGroupResult(): 8 | 9 | @classmethod 10 | def fail(cls, result, type_num): 11 | if type_num == ExecTypes.login: 12 | reason = result.get("reason") 13 | mobile = result.get("phone") 14 | 15 | if reason == 403: # 封号 16 | mobile_update(mobile, reason) 17 | 18 | queue = result.pop('queue') 19 | result["type_num"] = type_num 20 | queue.put(result) 21 | 22 | elif type_num == ExecTypes.group_join: 23 | queue = result.pop('queue') 24 | result["type_num"] = type_num 25 | queue.put(result) 26 | elif type_num == ExecTypes.send_text: 27 | queue = result.pop('queue') 28 | result["type_num"] = type_num 29 | queue.put(result) 30 | 31 | @classmethod 32 | def success(cls, result, type_num): 33 | if type_num == ExecTypes.group_kick: 34 | return 35 | phone = result.get("phone") 36 | data = result.get("data") 37 | 38 | dataPenetrate = SingleonConfig().getParamsAction() 39 | upload_url = dataPenetrate.get("PlayGroupUploadUrl") 40 | 41 | if type_num == ExecTypes.login: 42 | logger.info("{}:炒群号码登录成功".format(phone)) 43 | elif type_num == ExecTypes.group_join: 44 | dataPenetrate[phone] = type_num 45 | SingleonConfig().setParamsAction(dataPenetrate) 46 | PlayGroupHandle.upload(data, "AddGroup", upload_url, "AddGroupSuccessed", phone) 47 | elif type_num == ExecTypes.send_text: 48 | PlayGroupHandle.upload(data, "SendMessage", upload_url, "SendMessageSuccessed", phone) 49 | -------------------------------------------------------------------------------- /messagestack/register.py: -------------------------------------------------------------------------------- 1 | from common.singleonConfig import SingleonConfig 2 | from config.enumExec import ExecTypes 3 | from mycli.cli.register_result import ResultMq 4 | from mycli.mysql.mysql_cli import mobile_insert 5 | 6 | class RegisterResult(): 7 | 8 | @classmethod 9 | def fail(cls, result, type_num): 10 | 11 | if type_num == ExecTypes.login: 12 | mobile = result.get("phone") 13 | reason = result.get("reason") 14 | 15 | dataPenetrate = SingleonConfig().getParamsAction() 16 | is_mq = dataPenetrate.get("is_mq") 17 | RabbitMq = dataPenetrate.get("RabbitMq") 18 | master_id = dataPenetrate.get("master_id") 19 | message = "{}:注册登录失败,{}".format(mobile, reason) 20 | ResultMq.send_mq(master_id, message, is_mq, mq_config=RabbitMq) 21 | 22 | dataPenetrate["login"] = True 23 | dataPenetrate["fail"] = True 24 | SingleonConfig().setGroupControlAction(dataPenetrate) 25 | 26 | @classmethod 27 | def success(cls, result, type_num): 28 | 29 | if type_num == ExecTypes.login: 30 | data = result.get("data") 31 | mobile = result.get("phone") 32 | 33 | dataPenetrate = SingleonConfig().getParamsAction() 34 | action = dataPenetrate.get("action") 35 | RabbitMq = dataPenetrate.get("RabbitMq") 36 | is_mq = dataPenetrate.get("is_mq") 37 | master_id = dataPenetrate.get("master_id") 38 | 39 | if data.get("isFirst"): 40 | cc = data.get("cc") 41 | ca = data.get("country") 42 | mobile_insert(mobile, cc, ca, action, master_id) 43 | message = "{}:注册登录成功".format(mobile) 44 | ResultMq.send_mq(master_id, message, is_mq, mq_config=RabbitMq) 45 | 46 | dataPenetrate["login"] = True 47 | dataPenetrate["success"] = True 48 | SingleonConfig().setGroupControlAction(dataPenetrate) 49 | 50 | -------------------------------------------------------------------------------- /registration/__init__.py: -------------------------------------------------------------------------------- 1 | from .coderequest import WACodeRequest 2 | from .existsrequest import WAExistsRequest 3 | from .regrequest import WARegRequest 4 | from .clientLogRequest import WAClientLogRequest 5 | 6 | 7 | import logging 8 | 9 | logger = logging.getLogger(__name__) 10 | ch = logging.StreamHandler() 11 | # ch.setLevel(logging.DEBUG) 12 | 13 | formatter = logging.Formatter('%(levelname).1s %(asctime)s %(name)s - %(message)s') 14 | 15 | ch.setFormatter(formatter) 16 | 17 | logger.addHandler(ch) -------------------------------------------------------------------------------- /registration/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/registration/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /registration/__pycache__/coderequest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/registration/__pycache__/coderequest.cpython-38.pyc -------------------------------------------------------------------------------- /registration/__pycache__/existsrequest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/registration/__pycache__/existsrequest.cpython-38.pyc -------------------------------------------------------------------------------- /registration/__pycache__/regrequest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/registration/__pycache__/regrequest.cpython-38.pyc -------------------------------------------------------------------------------- /registration/clientLogRequest.py: -------------------------------------------------------------------------------- 1 | from common.http.warequest import WARequest 2 | from common.http.waresponseparser import JSONResponseParser 3 | from env import MyEnv 4 | 5 | 6 | class WAClientLogRequest(WARequest): 7 | 8 | def __init__(self, config): 9 | """ 10 | :param config: 11 | :type config: Config 12 | """ 13 | super(WAClientLogRequest,self).__init__(config) 14 | if config.id is None: 15 | raise ValueError("Config does not contain id") 16 | 17 | self.url = "v.whatsapp.net/v2/client_log" 18 | 19 | self.pvars = ["status", "login"] 20 | self.setParser(JSONResponseParser()) 21 | self.addParam("token", MyEnv.getCurrent().getToken(self._p_in)) 22 | -------------------------------------------------------------------------------- /registration/coderequest.py: -------------------------------------------------------------------------------- 1 | from common.http.warequest import WARequest 2 | from common.http.waresponseparser import JSONResponseParser 3 | from common.tools import WATools 4 | from registration.existsrequest import WAExistsRequest 5 | from registration.clientLogRequest import WAClientLogRequest 6 | 7 | from env import MyEnv 8 | 9 | 10 | class WACodeRequest(WARequest): 11 | def __init__(self, method, config): 12 | """ 13 | :type method: str 14 | :param config: 15 | :type config: Config 16 | """ 17 | super(WACodeRequest,self).__init__(config) 18 | 19 | self.addParam("sim_mcc", config.sim_mcc.zfill(3)) 20 | self.addParam("sim_mnc", config.sim_mnc.zfill(3)) 21 | self.addParam("method", method) 22 | self.addParam("token", MyEnv.getCurrent().getToken(self._p_in)) 23 | 24 | self.url = "v.whatsapp.net/v2/code" 25 | 26 | self.pvars = ["status","reason","length", "method", "retry_after", "code", "param"] +\ 27 | ["login", "type", "sms_wait", "voice_wait", "notify_after"] 28 | self.setParser(JSONResponseParser()) 29 | 30 | def send(self, parser = None, encrypt=True, preview=False): 31 | if self._config.id is not None: 32 | request = WAExistsRequest(self._config) 33 | result = request.send(encrypt=encrypt, preview=preview) 34 | 35 | if result: 36 | if result["status"] == "ok": 37 | return result 38 | elif result["status"] == "fail" and "reason" in result and result["reason"] == "blocked": 39 | return result 40 | else: 41 | self._config.id = WATools.generateIdentity() 42 | self.addParam("id", self._config.id) 43 | 44 | request = WAExistsRequest(self._config) 45 | result = request.send(encrypt=encrypt, preview=preview) 46 | print(result) 47 | request1 = WAClientLogRequest(self._config) 48 | result1 = request1.send(encrypt=encrypt, preview=preview) 49 | print(result1) 50 | self._config.backup_token = WATools.generateBackupToken() 51 | self.addParam("backup_token", self._config.backup_token) 52 | 53 | 54 | res = super(WACodeRequest, self).send(parser, encrypt=encrypt, preview=preview) 55 | 56 | return res 57 | -------------------------------------------------------------------------------- /registration/existsrequest.py: -------------------------------------------------------------------------------- 1 | from common.http.warequest import WARequest 2 | from common.http.waresponseparser import JSONResponseParser 3 | from env import MyEnv 4 | 5 | 6 | class WAExistsRequest(WARequest): 7 | 8 | def __init__(self, config): 9 | """ 10 | :param config: 11 | :type config: Config 12 | """ 13 | super(WAExistsRequest,self).__init__(config) 14 | if config.id is None: 15 | raise ValueError("Config does not contain id") 16 | 17 | self.url = "v.whatsapp.net/v2/exist" 18 | # "reason" : "incorrect", 19 | # "sms_wait" : 0, 20 | # "status" : "fail", 21 | # "flash_type" : 0, 22 | # "sms_length" : 6, 23 | # "voice_length" : 6, 24 | # "voice_wait" : 0, 25 | # "login" : "85295640514" 26 | self.pvars = ["status", "reason", "sms_length", "voice_length", "result","param", "login", "type", 27 | "chat_dns_domain", "edge_routing_info" 28 | ] 29 | 30 | self.setParser(JSONResponseParser()) 31 | self.addParam("token", MyEnv.getCurrent().getToken(self._p_in)) 32 | -------------------------------------------------------------------------------- /registration/regrequest.py: -------------------------------------------------------------------------------- 1 | from common.http.warequest import WARequest 2 | from common.http.waresponseparser import JSONResponseParser 3 | 4 | 5 | class WARegRequest(WARequest): 6 | 7 | def __init__(self, config, code): 8 | """ 9 | :param config: 10 | :type config: Config 11 | :param code: 12 | :type code: str 13 | """ 14 | super(WARegRequest,self).__init__(config) 15 | 16 | if config.id is None: 17 | raise ValueError("config.id is not set.") 18 | 19 | self.addParam("code", code) 20 | 21 | self.url = "v.whatsapp.net/v2/register" 22 | 23 | self.pvars = ["status", "login", "autoconf_type", "security_code_set", "type", "reason", "edge_routing_info", "chat_dns_domain" 24 | "reason",] 25 | 26 | self.setParser(JSONResponseParser()) 27 | -------------------------------------------------------------------------------- /resource/WA_Security_WhitePaper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/resource/WA_Security_WhitePaper.pdf -------------------------------------------------------------------------------- /versions/whatsapp_2.21.110.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reversePublic/whatsappShare/1ffa268cd4fa60bb39ad3aefbe30f0417b29bf04/versions/whatsapp_2.21.110.zip --------------------------------------------------------------------------------