Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added PyMicroChat.zip
Binary file not shown.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ Windows环境
>> (PyMicroChat) D:\VSCODE\PyMicroChat>python run.py

> 退出
>> (PyMicroChat) D:\VSCODE\PyMicroChat>deactivate
>> (PyMicroChat) D:\VSCODE\PyMicroChat>deactivate

### 工程中的dll文件源码地址:https://github.com/InfiniteTsukuyomi/MicroChat/tree/master/test/ecdh

##### QQ交流群:306804262
249 changes: 148 additions & 101 deletions microchat/Util.py

Large diffs are not rendered by default.

458 changes: 439 additions & 19 deletions microchat/business.py

Large diffs are not rendered by default.

99 changes: 68 additions & 31 deletions microchat/client_tornado.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from tornado import gen
from tornado import gen, iostream
from tornado.tcpclient import TCPClient
from tornado.ioloop import IOLoop
from tornado.ioloop import PeriodicCallback
import struct
from .dns_ip import get_ips
from . import dns_ip
from . import interface
from . import Util
from . import business
Expand All @@ -29,6 +29,7 @@
CMDID_REPORT_KV_REQ = 1000000190 #通知服务器消息已接收

#解包结果
UNPACK_NEED_RESTART = -2 # 需要切换DNS重新登陆
UNPACK_FAIL = -1 #解包失败
UNPACK_CONTINUE = 0 #封包不完整,继续接收数据
UNPACK_OK = 1 #解包成功
Expand Down Expand Up @@ -59,36 +60,57 @@ def __init__(self, ioloop, recv_cb, host, port, usr_name, passwd):
self.seq = 1
self.login_aes_key = b''
self.recv_data = b''
self.heartbeat_callback = None

@gen.coroutine
def start(self):
self.stream = yield TCPClient().connect(self.host, self.port)
wait_sec = 10
while True:
try:
self.stream = yield TCPClient().connect(self.host, self.port)
break
except iostream.StreamClosedError:
logger.error("connect error and again")
yield gen.sleep(wait_sec)
wait_sec = (wait_sec if (wait_sec >= 60) else (wait_sec * 2))

self.send_heart_beat()
# self.stream.read_until(b'\n', self.__recv)
self.longin()
self.heartbeat_callback = PeriodicCallback(self.send_heart_beat, 1000 * HEARTBEAT_TIMEOUT)
self.heartbeat_callback.start() # start scheduler
self.login()
self.stream.read_bytes(16, self.__recv_header)

@gen.coroutine
def restart(self, host, port):
if self.heartbeat_callback:
# 停止心跳
self.heartbeat_callback.stop()
self.host = host
self.port = port
self.stream.set_close_callback(self.__closed)
yield self.stream.close()

def __closed(self):
self.start()

def send_heart_beat(self):
logger.debug(
'last_heartbeat_time={},Util.get_utc() - last_heartbeat_time = {}'.
format(self.last_heartbeat_time,
Util.get_utc() - self.last_heartbeat_time))
logger.debug('last_heartbeat_time = {}, elapsed_time = {}'.format(self.last_heartbeat_time, Util.get_utc() - self.last_heartbeat_time))
#判断是否需要发送心跳包
if (Util.get_utc() - self.last_heartbeat_time) > HEARTBEAT_TIMEOUT:
if (Util.get_utc() - self.last_heartbeat_time) >= HEARTBEAT_TIMEOUT:
#长链接发包
send_data = self.pack(CMDID_NOOP_REQ)
self.stream.write(send_data)
self.send(send_data)
#记录本次发送心跳时间
self.last_heartbeat_time = Util.get_utc()
return True
else:
return False

def longin(self):
def login(self):
(login_buf, self.login_aes_key) = business.login_req2buf(
self.usr_name, self.passwd)
send_data = self.pack(CMDID_MANUALAUTH_REQ, login_buf)
self.stream.write(send_data)
self.send(send_data)

@gen.coroutine
def __recv_header(self, data):
Expand All @@ -98,9 +120,12 @@ def __recv_header(self, data):
(len_ack, _, _) = struct.unpack('>I4xII', data)
if self.recv_cb:
self.recv_cb(data)
# yield self.stream.read_until(b'\n', self.__recv)
yield self.stream.read_bytes(len_ack - 16, self.__recv_payload)

try:
yield self.stream.read_bytes(len_ack - 16, self.__recv_payload)
except iostream.StreamClosedError:
logger.error("stream read error, TCP disconnect and restart")
self.restart(dns_ip.fetch_longlink_ip(), 443)

@gen.coroutine
def __recv_payload(self, data):
logger.debug('recive from the server', data)
Expand All @@ -110,17 +135,29 @@ def __recv_payload(self, data):
if data != b'':
(ret, buf) = self.unpack(self.recv_data)
if UNPACK_OK == ret:
(ret, buf) = self.unpack(buf)
while UNPACK_OK == ret:
(ret, buf) = self.unpack(buf)
#刷新心跳
self.send_heart_beat()
# yield self.stream.read_until(b'\n', self.__recv)
yield self.stream.read_bytes(16, self.__recv_header)

@gen.coroutine
if UNPACK_NEED_RESTART == ret: # 需要切换DNS重新登陆
if dns_ip.dns_retry_times > 0:
self.restart(dns_ip.fetch_longlink_ip(),443)
return
else:
logger.error('切换DNS尝试次数已用尽,程序即将退出............')
self.stop()
try:
yield self.stream.read_bytes(16, self.__recv_header)
except iostream.StreamClosedError:
logger.error("stream read error, TCP disconnect and restart")
self.restart(dns_ip.fetch_longlink_ip(), 443)

def send(self, data):
yield self.stream.write(data.encode('utf-8'))
try:
self.stream.write(data)
except iostream.StreamClosedError:
logger.error("stream write error, TCP disconnect and restart")
self.restart(dns_ip.fetch_longlink_ip(), 443)

def stop(self):
self.ioloop.stop()
Expand Down Expand Up @@ -170,7 +207,7 @@ def unpack(self, buf):
self.ioloop.stop()
return (UNPACK_FAIL, b'')

self.stream.write(
self.send(
self.pack(CMDID_REPORT_KV_REQ,
business.sync_done_req2buf())) #通知服务器消息已接收
else: #sync key不存在
Expand All @@ -182,10 +219,12 @@ def unpack(self, buf):
elif CMDID_MANUALAUTH_REQ == cmd_id: #登录响应
code = business.login_buf2Resp(buf[16:len_ack],self.login_aes_key)
if -106 == code:
#logger.error('请再次登录!')
# 授权后,尝试自动重新登陆
logger.info('正在重新登陆........................',14)
self.longin()
# 授权后,尝试自动重新登陆
logger.info('正在重新登陆........................', 14)
self.login()
elif -301 == code:
logger.info('正在重新登陆........................', 14)
return (UNPACK_NEED_RESTART, b'')
elif code:
# raise RuntimeError('登录失败!') #登录失败
self.ioloop.stop()
Expand All @@ -196,11 +235,9 @@ def unpack(self, buf):


def start(wechat_usrname, wechat_passwd):
interface.init_all()
ioloop = IOLoop.instance()
_, szlong_ip = get_ips()
interface.InitAll()
tcp_client = ChatClient(ioloop=ioloop, usr_name=wechat_usrname, passwd=wechat_passwd, recv_cb=recv_data_handler,
host=szlong_ip[0], port=443)
host=dns_ip.fetch_longlink_ip(), port=443)
tcp_client.start()
PeriodicCallback(tcp_client.send_heart_beat, 1000*60).start() # start scheduler
ioloop.start()
13 changes: 7 additions & 6 deletions microchat/define.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
__CLIENT_VERSION__ = 637927472
__GUID__ = "A31d1d52a153928"
__CLIENT_SEQID__ = __GUID__ + "_1520395200010"
__CLIENT_SEQID_SIGN__ = "e89b245e12c398afbf39eb65f9978e19"
__IMEI__ = "865310173290678"
__ANDROID_ID__ = "eabc1f251716a49f"
__ANDROID_VER__ = "android-26"
GUID = "Aff0aef642a31fc2"
__GUID__ = GUID[:15]
__CLIENT_SEQID__ = GUID + "_1522827110765"
__CLIENT_SEQID_SIGN__ = "e89b158e4bcf988ebd09eb83f5378e87"
__IMEI__ = "865166024671219"
__ANDROID_ID__ = "d3151233cfbb4fd4"
__ANDROID_VER__ = "android-22"
__MANUFACTURER__ = "iPhone"
__MODELNAME__ = "X"
__MOBILE_WIFI_MAC_ADDRESS__ = "01:61:19:58:78:d3"
Expand Down
83 changes: 72 additions & 11 deletions microchat/dns_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,85 @@
'''

import requests
import os
import sqlite3
from bs4 import BeautifulSoup
from random import choice

# 登录返回-301自动切换DNS
dns_retry_times = 3

# 短链接ip池
short_ip = []

# 长链接ip池
long_ip = []

# dns db
conn_dns = None

def get_ips():
'''访问'http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns',
返回短ip列表szshort_ip,长ip列表szlong_ip.
返回短链接ip列表short_ip,长链接ip列表long_ip.
'''
szshort_ip = []
szlong_ip = []

ret = requests.get(
'http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns')
soup = BeautifulSoup(ret.text, "html.parser")
szshort_weixin = soup.find(
'domain', attrs={'name': 'szshort.weixin.qq.com'})
[szshort_ip.append(ip.get_text()) for ip in szshort_weixin.select('ip')]
szlong_weixin = soup.find(
'domain', attrs={'name': 'szlong.weixin.qq.com'})
[szlong_ip.append(ip.get_text()) for ip in szlong_weixin.select('ip')]

return szshort_ip, szlong_ip
short_weixin = soup.find(
'domain', attrs={'name': 'short.weixin.qq.com'})
[short_ip.append(ip.get_text()) for ip in short_weixin.select('ip')]
long_weixin = soup.find(
'domain', attrs={'name': 'long.weixin.qq.com'})
[long_ip.append(ip.get_text()) for ip in long_weixin.select('ip')]

return short_ip, long_ip

# 随机取出一个长链接ip地址
def fetch_longlink_ip():
if not long_ip:
get_ips()
return choice(long_ip)

# 随机取出一个短链接ip地址
def fetch_shortlink_ip():
if not short_ip:
get_ips()
return choice(short_ip)

# 尝试从db加载dns
def load_dns():
global conn_dns,short_ip,long_ip
# 建db文件夹
if not os.path.exists(os.getcwd() + '/db'):
os.mkdir(os.getcwd() + '/db')
# 建库
conn_dns = sqlite3.connect('./db/dns.db')
cur = conn_dns.cursor()
# 建dns表(保存上次登录时的dns)
cur.execute('create table if not exists dns(host varchar(1024) unique, ip varchar(1024))')
# 加载dns
try:
cur.execute('select ip from dns where host = "short.weixin.qq.com"')
row = cur.fetchone()
if row:
short_ip = row[0].split(',')
cur.execute('select ip from dns where host = "long.weixin.qq.com"')
row = cur.fetchone()
if row:
long_ip = row[0].split(',')
except:
pass
return

# 保存dns到db
def save_dns():
try:
conn_dns.commit()
conn_dns.execute('delete from dns')
conn_dns.execute('insert into dns(host,ip) values("{}","{}")'.format('short.weixin.qq.com', ','.join(short_ip)))
conn_dns.execute('insert into dns(host,ip) values("{}","{}")'.format('long.weixin.qq.com', ','.join(long_ip)))
conn_dns.commit()
except:
pass
return
Empty file added microchat/ecdh/__init__.py
Empty file.
Loading