├── .gitattributes ├── .gitignore ├── README.md ├── __init__.py ├── bot.py ├── conf.ini ├── get_group_list.py ├── img └── .gitignore ├── main.xlsx ├── parse_excel.py ├── parseconf.py ├── run.ico ├── run.py ├── schedule.py ├── temp └── img │ └── .gitignore ├── template ├── notice.txt └── welcome.txt ├── tkl.py ├── wxbot.py └── 微信淘宝推广机器人v0.3.12_sign_win.zip /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | *.pyc 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear on external disk 36 | .Spotlight-V100 37 | .Trashes 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | temp/*.* 47 | *.data -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Morobot 微信优惠券推广机器人 2 | 3 | ## 使用说明 4 | 1. 对应的群需要保存到通讯录 5 | 2. 设置`conf.ini` 6 | - 一般只需要修改robot的内容,具体参考conf.ini中的说明 7 | 3. 运行`run.exe` 8 | 4. 弹出二维码, 扫描登录即可 9 | 5. 程序会隔一段时间检查是否有新数据,添加新数据直接在main.xlsx新行添加 10 | 11 | ## QA 12 | - 如何重置excel下次开始行号? 13 | 14 | 删除excel.data文件即可 15 | 16 | - 如何修改新成员欢迎信息? 17 | 18 | conf.ini中welcome对应的文件,打开修改即可,实时生效 19 | 20 | ## 注意 21 | - 文件路径,目录尽量不要中文名。 22 | - 对设置的群一定要先保存到通讯录。 23 | 24 | ## 交流群 25 | QQ: [219974900](https://shang.qq.com/wpa/qunwpa?idkey=b01ae3447f0a9341eb7b13f41ce4ead7fed68a2e6ef79f48c89da96ce8c8a7ed) -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiehe/wechat-coupon-robot/57f85be01d37480164f915cc2ca846eb3a82b004/__init__.py -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | from wxbot import * 5 | import ConfigParser 6 | import json 7 | 8 | 9 | class TulingWXBot(WXBot): 10 | def __init__(self): 11 | WXBot.__init__(self) 12 | 13 | self.tuling_key = "" 14 | self.robot_switch = True 15 | 16 | try: 17 | cf = ConfigParser.ConfigParser() 18 | cf.read('conf.ini') 19 | self.tuling_key = cf.get('main', 'key') 20 | except Exception: 21 | pass 22 | print 'tuling_key:', self.tuling_key 23 | 24 | def tuling_auto_reply(self, uid, msg): 25 | if self.tuling_key: 26 | url = "http://www.tuling123.com/openapi/api" 27 | user_id = uid.replace('@', '')[:30] 28 | body = {'key': self.tuling_key, 'info': msg.encode('utf8'), 'userid': user_id} 29 | r = requests.post(url, data=body) 30 | respond = json.loads(r.text) 31 | result = '' 32 | if respond['code'] == 100000: 33 | result = respond['text'].replace('
', ' ') 34 | elif respond['code'] == 200000: 35 | result = respond['url'] 36 | elif respond['code'] == 302000: 37 | for k in respond['list']: 38 | result = result + u"【" + k['source'] + u"】 " +\ 39 | k['article'] + "\t" + k['detailurl'] + "\n" 40 | else: 41 | result = respond['text'].replace('
', ' ') 42 | 43 | print ' ROBOT:', result 44 | return result 45 | else: 46 | return u"知道啦" 47 | 48 | def auto_switch(self, msg): 49 | msg_data = msg['content']['data'] 50 | stop_cmd = [u'退下', u'走开', u'关闭', u'关掉', u'休息', u'滚开'] 51 | start_cmd = [u'出来', u'启动', u'工作'] 52 | if self.robot_switch: 53 | for i in stop_cmd: 54 | if i == msg_data: 55 | self.robot_switch = False 56 | self.send_msg_by_uid(u'[Robot]' + u'机器人已关闭!', msg['to_user_id']) 57 | else: 58 | for i in start_cmd: 59 | if i == msg_data: 60 | self.robot_switch = True 61 | self.send_msg_by_uid(u'[Robot]' + u'机器人已开启!', msg['to_user_id']) 62 | 63 | def handle_msg_all(self, msg): 64 | if not self.robot_switch and msg['msg_type_id'] != 1: 65 | return 66 | if msg['msg_type_id'] == 1 and msg['content']['type'] == 0: # reply to self 67 | self.auto_switch(msg) 68 | elif msg['msg_type_id'] == 4 and msg['content']['type'] == 0: # text message from contact 69 | self.send_msg_by_uid(self.tuling_auto_reply(msg['user']['id'], msg['content']['data']), msg['user']['id']) 70 | elif msg['msg_type_id'] == 3 and msg['content']['type'] == 0: # group text message 71 | if 'detail' in msg['content']: 72 | my_names = self.get_group_member_name(self.my_account['UserName'], msg['user']['id']) 73 | if my_names is None: 74 | my_names = {} 75 | if 'NickName' in self.my_account and self.my_account['NickName']: 76 | my_names['nickname2'] = self.my_account['NickName'] 77 | if 'RemarkName' in self.my_account and self.my_account['RemarkName']: 78 | my_names['remark_name2'] = self.my_account['RemarkName'] 79 | 80 | is_at_me = False 81 | for detail in msg['content']['detail']: 82 | if detail['type'] == 'at': 83 | for k in my_names: 84 | if my_names[k] and my_names[k] == detail['value']: 85 | is_at_me = True 86 | break 87 | if is_at_me: 88 | src_name = msg['content']['user']['name'] 89 | reply = 'to ' + src_name + ': ' 90 | if msg['content']['type'] == 0: # text message 91 | reply += self.tuling_auto_reply(msg['content']['user']['id'], msg['content']['desc']) 92 | else: 93 | reply += u"对不起,只认字,其他杂七杂八的我都不认识,,,Ծ‸Ծ,," 94 | self.send_msg_by_uid(reply, msg['user']['id']) 95 | 96 | 97 | def main(): 98 | bot = TulingWXBot() 99 | bot.DEBUG = True 100 | bot.conf['qr'] = 'png' 101 | 102 | bot.run() 103 | 104 | 105 | if __name__ == '__main__': 106 | main() 107 | 108 | -------------------------------------------------------------------------------- /conf.ini: -------------------------------------------------------------------------------- 1 | [robot] 2 | # 微信群名称 3 | group_name = 测试 4 | 5 | # 定时发送优惠商品间隔(秒数) 6 | schedule_sleep = 60 7 | 8 | # 新人加群欢迎语 9 | welcome = template/welcome.txt 10 | 11 | # 新人加群提醒 12 | notice = template/notice.txt 13 | 14 | 15 | [excel_parse] 16 | 17 | # excel文件路径 18 | excel_file = main.xlsx 19 | 20 | # 开始行数, 从0开始算 21 | start_line = 1 22 | 23 | # 优惠券时间验证可选 0:验证(默认) 1:不验证 24 | ignore_coupon_date = 0 25 | 26 | # 列配置, 从0开始算 27 | # 标题 28 | title_col = 1 29 | # 主图 30 | pic_col = 2 31 | # 详情url 32 | detail_url_col = 3 33 | # 商品价格 34 | price_col = 6 35 | # 店铺名称 36 | shop_col = 12 37 | # 优惠券 38 | coupon_col = 17 39 | # 优惠券开始 40 | coupon_start_col = 18 41 | # 优惠券结束 42 | coupon_end_col = 19 43 | # 商品优惠券推广地址 44 | coupon_campaign_col = 21 -------------------------------------------------------------------------------- /get_group_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | from wxbot import * 5 | 6 | 7 | class MyWXBot(WXBot): 8 | def handle_msg_all(self, msg): 9 | if msg['msg_type_id'] == 4 and msg['content']['type'] == 0: 10 | self.send_msg_by_uid(u'hi', msg['user']['id']) 11 | #self.send_img_msg_by_uid("img/1.png", msg['user']['id']) 12 | #self.send_file_msg_by_uid("img/1.png", msg['user']['id']) 13 | ''' 14 | def schedule(self): 15 | self.send_msg(u'张三', u'测试') 16 | time.sleep(1) 17 | ''' 18 | 19 | 20 | def main(): 21 | bot = MyWXBot() 22 | bot.DEBUG = True 23 | bot.conf['qr'] = 'png' 24 | bot.run() 25 | 26 | 27 | if __name__ == '__main__': 28 | main() 29 | -------------------------------------------------------------------------------- /img/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /main.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiehe/wechat-coupon-robot/57f85be01d37480164f915cc2ca846eb3a82b004/main.xlsx -------------------------------------------------------------------------------- /parse_excel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | from tkl import Tkl 5 | import xlrd 6 | import re 7 | import time 8 | import sys 9 | reload(sys) 10 | sys.setdefaultencoding('utf8') 11 | 12 | """ 13 | 解析excel数据 14 | """ 15 | class ParseExcel: 16 | def __init__(self, conf): 17 | self.conf = conf 18 | 19 | def get_line_str(self): 20 | """ 21 | 根据上次行数+1,获取该行数据组合 22 | """ 23 | excel = xlrd.open_workbook(self.conf.excel_file) 24 | table = excel.sheet_by_index(0) 25 | rows = table.nrows 26 | data = { 27 | "text": "", 28 | "image_url": "", 29 | } 30 | 31 | # 没有要执行的数据 32 | if rows == 0 or (rows - self.conf.start_line) == 0: 33 | return '' 34 | 35 | # 根据上次执行的行号处理 36 | current_line = self.get_last_time_line() 37 | 38 | # 上次行号后是否有新数据 39 | if current_line >= rows or (current_line + 1) >= rows: 40 | print '[INFO] No new line to execute in excel.' 41 | return '' 42 | current_line = current_line + 1 43 | row = table.row_values(current_line) 44 | 45 | # 优惠券是否过期 46 | if self.get_coupon_is_expired(row) == True: 47 | print '[INFO] Line %d coupon is not in the date range.' % (current_line) 48 | else: 49 | # 组装字符串 50 | # 标题 51 | data['text'] = row[self.conf.title_col] + '\n' 52 | # 获得使用优惠券后的价格后,组装价格一行数据 53 | coupon = self.get_coupon_price(row[self.conf.price_col], row[self.conf.coupon_col]) 54 | if coupon['math'] == 1: 55 | data['text'] += u'在售%s元,券后%s元 \n' % (row[self.conf.price_col], coupon['coupon_price']) 56 | else: 57 | data['text'] += u'%s元起,%s \n' % (row[self.conf.price_col], row[self.conf.coupon_col]) 58 | data['text'] += '--------- \n'; 59 | # 店铺信息 60 | data['text'] += u'来自%s \n' % (row[self.conf.shop_col]) 61 | # 淘口令信息 62 | tkl = Tkl() 63 | code = tkl.get_code(row[self.conf.coupon_campaign_col]) 64 | if code == '': 65 | print '[ERROR] 生成淘口令失败' 66 | raw_input(u"按任意键结束...") 67 | exit() 68 | data['text'] += code 69 | # 主图信息 70 | data['image_url'] = row[self.conf.pic_col] 71 | 72 | # 写入当前成功执行的行数 73 | self.write_current_line(current_line) 74 | 75 | return data 76 | 77 | def get_coupon_price(slef, price, coupon_info): 78 | """ 79 | 获得使用优惠券后的价格 80 | """ 81 | data = { 82 | "match": 0, # 是否满足优惠券满减 83 | "coupon_price": 0.0, # 使用优惠券后的金额 84 | "coupon": [0, 0] # 满减的两个价格 85 | } 86 | m = re.findall(r'(\d+)', coupon_info) 87 | if len(m) == 2: 88 | # 满x元减x元 89 | data['coupon'] = [int(i) for i in m] 90 | elif len(m) == 1: 91 | data['coupon'] = [0, int(m[0])] 92 | else: 93 | print u'[ERROR] 优惠券格式不正确' 94 | 95 | # 如果没有达到优惠券指定金额则返回原价 96 | price = float(price) 97 | if price >= data['coupon'][0]: 98 | data['math'] = 1 99 | data['coupon_price'] = price - data['coupon'][1] 100 | else: 101 | data['math'] = 0 102 | 103 | return data 104 | 105 | def get_coupon_is_expired(self, row): 106 | """ 107 | 判断优惠券是否过期,或未开始 108 | return bool 109 | """ 110 | # 是否忽略时间验证 111 | if self.conf.ignore_coupon_date == 1: 112 | return False 113 | 114 | # date = time.strftime('%Y-%m-%d', time.localtime()) 115 | start = re.findall(r'\d{4}\-\d{2}\-\d{2}', row[self.conf.coupon_start_col]) 116 | end = re.findall(r'\d{4}\-\d{2}\-\d{2}', row[self.conf.coupon_end_col]) 117 | if len(start) == 1 and len(end) == 1: 118 | start = start[0] 119 | end = end[0] 120 | else: 121 | print u'[ERROR] 优惠券时间获取失败.' 122 | raw_input(u"按任意键结束...") 123 | exit() 124 | 125 | # 时间戳比较 126 | start_time = time.mktime(time.strptime(start, "%Y-%m-%d")) 127 | end_time = time.mktime(time.strptime(end, "%Y-%m-%d")) 128 | end_time = time.mktime(time.strptime('2016-12-04', "%Y-%m-%d")) 129 | cur_time = time.time() 130 | 131 | if start_time <= cur_time and end_time >= cur_time: 132 | return False 133 | else: 134 | print u'[WARNING] 优惠券过期[%s至%s].' % (row[self.conf.coupon_start_col], row[self.conf.coupon_end_col]) 135 | return True 136 | 137 | def get_last_time_line(self): 138 | """ 139 | 获取最后一次执行成功的excel行号 140 | return int 141 | """ 142 | try: 143 | f = open('excel.data', 'r') 144 | line = f.read() 145 | f.close() 146 | if line == '': 147 | line_num = 0 148 | else: 149 | line_num = int(line) 150 | except IOError, e: 151 | line_num = 0 152 | 153 | return line_num 154 | 155 | def write_current_line(self, line_num): 156 | """ 157 | 写入当前行 158 | """ 159 | f = open('excel.data', 'w') 160 | f.write(str(line_num)) 161 | f.close() -------------------------------------------------------------------------------- /parseconf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import ConfigParser 5 | import sys 6 | reload(sys) 7 | sys.setdefaultencoding('utf8') 8 | 9 | class ParseConf: 10 | def __init__(self): 11 | try: 12 | cf = ConfigParser.ConfigParser() 13 | cf.read('conf.ini') 14 | 15 | # 群名 16 | self.group_name = cf.get('robot', 'group_name') 17 | 18 | # 定时发送优惠商品间隔(秒数) 19 | self.schedule_sleep = int(cf.get('robot', 'schedule_sleep')) 20 | 21 | # 欢迎 22 | self.welcome = cf.get('robot', 'welcome') 23 | 24 | # 新人提醒 25 | self.notice = cf.get('robot', 'notice') 26 | 27 | # excel文件 28 | self.excel_file = cf.get('excel_parse', 'excel_file') 29 | 30 | # excel主要内容 开始行数 31 | self.start_line = int(cf.get('excel_parse', 'start_line')) 32 | 33 | # 优惠券时间验证可选 34 | self.ignore_coupon_date = int(cf.get('excel_parse', 'ignore_coupon_date')) 35 | 36 | # 列配置 37 | # 标题 38 | self.title_col = int(cf.get('excel_parse', 'title_col')) 39 | # 主图 40 | self.pic_col = int(cf.get('excel_parse', 'pic_col')) 41 | # 详情url 42 | self.detail_url_col = int(cf.get('excel_parse', 'detail_url_col')) 43 | # 商品价格 44 | self.price_col = int(cf.get('excel_parse', 'price_col')) 45 | # 店铺名称 46 | self.shop_col = int(cf.get('excel_parse', 'shop_col')) 47 | # 优惠券 48 | self.coupon_col = int(cf.get('excel_parse', 'coupon_col')) 49 | # 优惠券开始 50 | self.coupon_start_col = int(cf.get('excel_parse', 'coupon_start_col')) 51 | # 优惠券结束 52 | self.coupon_end_col = int(cf.get('excel_parse', 'coupon_end_col')) 53 | # 商品优惠券推广地址 54 | self.coupon_campaign_col = int(cf.get('excel_parse', 'coupon_campaign_col')) 55 | except Exception, e: 56 | print '[ERROR] File conf.ini is not exist.' 57 | raise e -------------------------------------------------------------------------------- /run.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiehe/wechat-coupon-robot/57f85be01d37480164f915cc2ca846eb3a82b004/run.ico -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | from wxbot import * 5 | from parseconf import ParseConf 6 | from parse_excel import ParseExcel 7 | from schedule import Schedule 8 | import os 9 | import requests 10 | import re 11 | import time 12 | import urllib2 13 | import hashlib 14 | import sys 15 | reload(sys) 16 | sys.setdefaultencoding('utf8') 17 | 18 | class MyWXBot(WXBot): 19 | def __init__(self): 20 | WXBot.__init__(self) 21 | self.config = ParseConf() 22 | 23 | def handle_msg_all(self, msg): 24 | # 群新成员欢迎 25 | # msg_type_id = 3是群聊 26 | # msg['content']['type'] == 12是邀请 27 | if msg['msg_type_id'] == 3 and msg['content']['type'] == 12: 28 | if msg['user']['name'] == self.config.group_name: 29 | m = re.findall(ur'邀请\"(.*)\"', msg['content']['data']) 30 | if len(m) == 1: 31 | invitee = m[0] # 被要求人名 32 | # 发送欢迎 33 | if self.config.welcome and os.path.exists(self.config.welcome): 34 | f = open(self.config.welcome, 'r') 35 | welcome = f.read() 36 | f.close() 37 | print '[INFO] Send welcome message to %s.' % (invitee) 38 | self.send_msg(self.config.group_name, u'@%s\n %s' % (invitee, welcome)) 39 | # 发送提醒 40 | if self.config.notice and os.path.exists(self.config.notice): 41 | f = open(self.config.notice, 'r') 42 | notice = f.read() 43 | f.close() 44 | print '[INFO] Send notice message to %s.' % (invitee) 45 | self.send_msg(self.config.group_name, u'@%s\n %s' % (invitee, notice)) 46 | 47 | def schedule(self): 48 | # self.send_msg(u'测试', u'机器人测试') 49 | # dst = self.get_user_id(u'测试') 50 | # self.send_img_msg_by_uid('b812c8fcc3cec3fdbddb1a8cd488d43f86942765.jpg', dst) 51 | 52 | s = Schedule(self.config.schedule_sleep) 53 | if s.is_valid_time() == True: 54 | dst = self.get_user_id(self.config.group_name) 55 | 56 | xls = ParseExcel(self.config) 57 | data = xls.get_line_str() 58 | 59 | if data == '': 60 | print u'[INFO] 没有需要发送的数据.' 61 | elif data['text'] and data['image_url']: 62 | # 下载商品图片 63 | img_data = urllib2.urlopen(data['image_url']).read() 64 | # 保存图片 65 | m = hashlib.md5() 66 | m.update(data['image_url']) 67 | name = "temp/img/%s.jpg" % (m.hexdigest()) 68 | print '[INFO] Download image as %s' % (name) 69 | f = open(name, 'wb') 70 | f.write(img_data) 71 | f.close() 72 | stat = self.send_img_msg_by_uid(name, dst) 73 | if stat == True: 74 | print u'[INFO] 图片发送成功 %s' % (dst) 75 | 76 | # 延迟2秒,防止图片比文字慢 77 | time.sleep(2) 78 | 79 | stat = self.send_msg(self.config.group_name, data['text']) 80 | if stat == True: 81 | print u'[INFO] 优惠信息发送成功. %s' % (dst) 82 | else: 83 | print u'[INFO] 没有图片和优惠码等信息.' 84 | 85 | # time.sleep(10) 86 | 87 | 88 | def main(): 89 | print u'version v0.3.12 By:小墨 QQ:244349067' 90 | bot = MyWXBot() 91 | # bot.DEBUG = True 92 | bot.conf['qr'] = 'png' 93 | bot.run() 94 | 95 | # 淘口令测试 96 | # tkl = Tkl() 97 | # print tkl.get_code("https://uland.taobao.com/coupon/edetail?e=7EVvU0WKZAMN%2BoQUE6FNzLZT%2FCtAkW38zXEUa5tVE6YLiN%2FThfFfO7tLmdOqMMWWQMaEildNVff21CRvce78ah0HgBdG%2FDDL%2F1M%2FBw7Sf%2FfhIA3IALBf2qTj%2FKNmxZP%2Bo8BWEgW6U2HKjYLBGfMnkUrGLMmMO24%2B&pid=mm_15647003_18732119_65972202&af=1") 98 | 99 | # 解析excel测试 100 | # xls = ParseExcel(ParseConf()) 101 | # print xls.get_line_str() 102 | 103 | if __name__ == '__main__': 104 | main() 105 | -------------------------------------------------------------------------------- /schedule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import time 5 | 6 | """ 7 | ::定时器:: 8 | 解决长时间sleep自动退出问题 9 | example: 10 | s = Schedule(60) 11 | print s.is_valid_time() 12 | """ 13 | class Schedule: 14 | def __init__(self, sleep_sec): 15 | self.sleep_sec = sleep_sec 16 | 17 | def is_valid_time(self): 18 | """判断当前是否在有时间内""" 19 | last_time = self.get_last_time() 20 | now_time = int(time.time()) 21 | if (last_time + self.sleep_sec) > now_time: 22 | return False 23 | elif last_time == 0: 24 | self.write_time() 25 | return True 26 | else: 27 | self.write_time() 28 | return True 29 | 30 | def get_last_time(self): 31 | """获取最后执行时间""" 32 | try: 33 | f = open('schedule.data', 'r') 34 | timestmap = f.read() 35 | if timestmap == '': 36 | return 0 37 | except IOError, e: 38 | return 0 39 | return int(timestmap) 40 | 41 | def write_time(self): 42 | """写入时间戳""" 43 | f = open('schedule.data', 'w') 44 | now_time = int(time.time()) 45 | f.write(str(now_time)) -------------------------------------------------------------------------------- /temp/img/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /template/notice.txt: -------------------------------------------------------------------------------- 1 | 购物须知:凡通过群内链接购物的亲们,先私信群主微信号再下单购物,群主后台有收录才能给亲发购物返现红包哦! -------------------------------------------------------------------------------- /template/welcome.txt: -------------------------------------------------------------------------------- 1 | 欢迎新人进群!本群主要为大家精选一些天猫、淘宝店的限时秒杀商品及内部优惠券,记得先领券再购物哦! 2 | 群规: 3 | 1. 晒单鼓励,大家通过群买到的商品,晒实物图到该群,6元以上晒一单奖励一块钱,私信群主领取。 4 | 2. 群内除群主、管理员外,禁止任何人发布商品链接! 5 | 3. 严禁乱加人,谨防广告骚扰或商业诈骗,如遇群内好友私信广告,欢迎举报!! 6 | 7 | 每天我们会精选一些限时秒杀商品分享到群中,通过群内分享购买的商品,晒单均有一元奖励哦! -------------------------------------------------------------------------------- /tkl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import requests 5 | import time 6 | import sys 7 | reload(sys) 8 | sys.setdefaultencoding('utf8') 9 | 10 | """ 11 | 淘口令生成器 12 | """ 13 | class Tkl: 14 | def __init__(self): 15 | self.tkl_url = { 16 | 'one': 'http://www.tuidian.net/?a=tpwd', 17 | 'two': 'http://tool.chaozhi.hk/api/tb/GetTkl.php', 18 | } 19 | # 次数限制 20 | self.tkl_limit = { 21 | 'one': -1, # 不限制 22 | 'two': 100, 23 | } 24 | # 当前使用的 25 | self.current = 'one' 26 | date = time.strftime('%Y-%m-%d', time.localtime()) 27 | try: 28 | line = self.get_data() 29 | self.current = line[0] 30 | # 如果不是当前则重置 31 | if line[1] != date: 32 | self.write_date('one') 33 | except IOError, e: 34 | self.write_date('one') 35 | 36 | def get_code(self, url): 37 | # return method_one(url) 38 | ret = getattr(self, 'method_'+self.current)(url) 39 | if ret == '' and self.current == 'one': 40 | self.write_date('two') 41 | self.current = 'two' 42 | ret = getattr(self, 'method_'+self.current)(url) 43 | 44 | # 是否次数用完 45 | if self.tkl_limit['two'] == 0 and self.current == 'two': 46 | print '[ERROR] 次数全部用完' 47 | 48 | return ret 49 | 50 | def method_one(self, url): 51 | """调用方式一""" 52 | payload = {'url': url} 53 | headers = { 54 | 'X-Requested-With':'XMLHttpRequest', 55 | 'User-Agent':"""Mozilla/5.0 (Windows NT 10.0; WOW64) 56 | AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36""", 57 | 'Referer':'http://www.tuidian.net/' 58 | } 59 | r = requests.post(self.tkl_url['one'], data=payload, headers=headers) 60 | json = r.json() 61 | if json['error'] == 1: 62 | return json['info'] 63 | else: 64 | return '' 65 | 66 | def method_two(self, url): 67 | payload = { 68 | 'url': url, 69 | 'text': '粉丝福利购', 70 | 'logo':'', 71 | 'action':'refresh' 72 | } 73 | headers = { 74 | 'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8', 75 | 'Referer':'http://tool.chaozhi.hk/tkl/', 76 | 'X-Requested-With':'XMLHttpRequest' 77 | } 78 | r = requests.post(self.tkl_url['two'], data=payload, headers=headers) 79 | json = r.json() 80 | if json['model'] != '': 81 | # 剩余次数 82 | self.tkl_limit['two'] = int(json['refresh']) 83 | return '复制这条信息,打开 [手机淘宝] 即可查看' + json['model'] 84 | else: 85 | return '' 86 | 87 | def get_data(self): 88 | f = open('tkl.data', 'r') 89 | line = f.read().split('|') 90 | f.close() 91 | return line 92 | 93 | def write_date(self, current): 94 | date = time.strftime('%Y-%m-%d', time.localtime()) 95 | f = open('tkl.data', 'w') 96 | f.write(current+'|'+date) 97 | f.close() -------------------------------------------------------------------------------- /wxbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import os 5 | import sys 6 | import traceback 7 | import webbrowser 8 | import pyqrcode 9 | import requests 10 | import mimetypes 11 | import json 12 | import xml.dom.minidom 13 | import urllib 14 | import time 15 | import re 16 | import random 17 | from traceback import format_exc 18 | from requests.exceptions import ConnectionError, ReadTimeout 19 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 20 | import HTMLParser 21 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 22 | 23 | UNKONWN = 'unkonwn' 24 | SUCCESS = '200' 25 | SCANED = '201' 26 | TIMEOUT = '408' 27 | 28 | 29 | def show_image(file_path): 30 | """ 31 | 跨平台显示图片文件 32 | :param file_path: 图片文件路径 33 | """ 34 | if sys.version_info >= (3, 3): 35 | from shlex import quote 36 | else: 37 | from pipes import quote 38 | 39 | if sys.platform == "darwin": 40 | command = "open -a /Applications/Preview.app %s&" % quote(file_path) 41 | os.system(command) 42 | else: 43 | webbrowser.open(os.path.join(os.getcwd(),'temp',file_path)) 44 | 45 | 46 | class SafeSession(requests.Session): 47 | def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, 48 | timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=False, cert=None, 49 | json=None): 50 | for i in range(3): 51 | try: 52 | return super(SafeSession, self).request(method, url, params, data, headers, cookies, files, auth, 53 | timeout, 54 | allow_redirects, proxies, hooks, stream, verify, cert, json) 55 | except Exception as e: 56 | print e.message, traceback.format_exc() 57 | continue 58 | 59 | 60 | class WXBot: 61 | """WXBot功能类""" 62 | 63 | def __init__(self): 64 | self.DEBUG = False 65 | self.uuid = '' 66 | self.base_uri = '' 67 | self.base_host = '' 68 | self.redirect_uri = '' 69 | self.uin = '' 70 | self.sid = '' 71 | self.skey = '' 72 | self.pass_ticket = '' 73 | self.device_id = 'e' + repr(random.random())[2:17] 74 | self.base_request = {} 75 | self.sync_key_str = '' 76 | self.sync_key = [] 77 | self.sync_host = '' 78 | 79 | #文件缓存目录 80 | self.temp_pwd = os.path.join(os.getcwd(),'temp') 81 | if os.path.exists(self.temp_pwd) == False: 82 | os.makedirs(self.temp_pwd) 83 | 84 | self.session = SafeSession() 85 | self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'}) 86 | self.conf = {'qr': 'png'} 87 | 88 | self.my_account = {} # 当前账户 89 | 90 | # 所有相关账号: 联系人, 公众号, 群组, 特殊账号 91 | self.member_list = [] 92 | 93 | # 所有群组的成员, {'group_id1': [member1, member2, ...], ...} 94 | self.group_members = {} 95 | 96 | # 所有账户, {'group_member':{'id':{'type':'group_member', 'info':{}}, ...}, 'normal_member':{'id':{}, ...}} 97 | self.account_info = {'group_member': {}, 'normal_member': {}} 98 | 99 | self.contact_list = [] # 联系人列表 100 | self.public_list = [] # 公众账号列表 101 | self.group_list = [] # 群聊列表 102 | self.special_list = [] # 特殊账号列表 103 | self.encry_chat_room_id_list = [] # 存储群聊的EncryChatRoomId,获取群内成员头像时需要用到 104 | 105 | self.file_index = 0 106 | 107 | @staticmethod 108 | def to_unicode(string, encoding='utf-8'): 109 | """ 110 | 将字符串转换为Unicode 111 | :param string: 待转换字符串 112 | :param encoding: 字符串解码方式 113 | :return: 转换后的Unicode字符串 114 | """ 115 | if isinstance(string, str): 116 | return string.decode(encoding) 117 | elif isinstance(string, unicode): 118 | return string 119 | else: 120 | raise Exception('Unknown Type') 121 | 122 | def get_contact(self): 123 | """获取当前账户的所有相关账号(包括联系人、公众号、群聊、特殊账号)""" 124 | url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \ 125 | % (self.pass_ticket, self.skey, int(time.time())) 126 | r = self.session.post(url, data='{}') 127 | r.encoding = 'utf-8' 128 | if self.DEBUG: 129 | with open(os.path.join(self.temp_pwd,'contacts.json'), 'w') as f: 130 | f.write(r.text.encode('utf-8')) 131 | dic = json.loads(r.text) 132 | self.member_list = dic['MemberList'] 133 | 134 | special_users = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail', 135 | 'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle', 136 | 'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp', 137 | 'blogapp', 'facebookapp', 'masssendapp', 'meishiapp', 138 | 'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder', 139 | 'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 140 | 'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11', 141 | 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages'] 142 | 143 | self.contact_list = [] 144 | self.public_list = [] 145 | self.special_list = [] 146 | self.group_list = [] 147 | 148 | for contact in self.member_list: 149 | if contact['VerifyFlag'] & 8 != 0: # 公众号 150 | self.public_list.append(contact) 151 | self.account_info['normal_member'][contact['UserName']] = {'type': 'public', 'info': contact} 152 | elif contact['UserName'] in special_users: # 特殊账户 153 | self.special_list.append(contact) 154 | self.account_info['normal_member'][contact['UserName']] = {'type': 'special', 'info': contact} 155 | elif contact['UserName'].find('@@') != -1: # 群聊 156 | self.group_list.append(contact) 157 | self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact} 158 | elif contact['UserName'] == self.my_account['UserName']: # 自己 159 | self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact} 160 | else: 161 | self.contact_list.append(contact) 162 | self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': contact} 163 | 164 | self.batch_get_group_members() 165 | 166 | for group in self.group_members: 167 | for member in self.group_members[group]: 168 | if member['UserName'] not in self.account_info: 169 | self.account_info['group_member'][member['UserName']] = \ 170 | {'type': 'group_member', 'info': member, 'group': group} 171 | 172 | if self.DEBUG: 173 | with open(os.path.join(self.temp_pwd,'contact_list.json'), 'w') as f: 174 | f.write(json.dumps(self.contact_list)) 175 | with open(os.path.join(self.temp_pwd,'special_list.json'), 'w') as f: 176 | f.write(json.dumps(self.special_list)) 177 | with open(os.path.join(self.temp_pwd,'group_list.json'), 'w') as f: 178 | f.write(json.dumps(self.group_list)) 179 | with open(os.path.join(self.temp_pwd,'public_list.json'), 'w') as f: 180 | f.write(json.dumps(self.public_list)) 181 | with open(os.path.join(self.temp_pwd,'member_list.json'), 'w') as f: 182 | f.write(json.dumps(self.member_list)) 183 | with open(os.path.join(self.temp_pwd,'group_users.json'), 'w') as f: 184 | f.write(json.dumps(self.group_members)) 185 | with open(os.path.join(self.temp_pwd,'account_info.json'), 'w') as f: 186 | f.write(json.dumps(self.account_info)) 187 | return True 188 | 189 | def batch_get_group_members(self): 190 | """批量获取所有群聊成员信息""" 191 | url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket) 192 | params = { 193 | 'BaseRequest': self.base_request, 194 | "Count": len(self.group_list), 195 | "List": [{"UserName": group['UserName'], "EncryChatRoomId": ""} for group in self.group_list] 196 | } 197 | r = self.session.post(url, data=json.dumps(params)) 198 | r.encoding = 'utf-8' 199 | dic = json.loads(r.text) 200 | group_members = {} 201 | encry_chat_room_id = {} 202 | for group in dic['ContactList']: 203 | gid = group['UserName'] 204 | members = group['MemberList'] 205 | group_members[gid] = members 206 | encry_chat_room_id[gid] = group['EncryChatRoomId'] 207 | self.group_members = group_members 208 | self.encry_chat_room_id_list = encry_chat_room_id 209 | 210 | def get_group_member_name(self, gid, uid): 211 | """ 212 | 获取群聊中指定成员的名称信息 213 | :param gid: 群id 214 | :param uid: 群聊成员id 215 | :return: 名称信息,类似 {"display_name": "test_user", "nickname": "test", "remark_name": "for_test" } 216 | """ 217 | if gid not in self.group_members: 218 | return None 219 | group = self.group_members[gid] 220 | for member in group: 221 | if member['UserName'] == uid: 222 | names = {} 223 | if 'RemarkName' in member and member['RemarkName']: 224 | names['remark_name'] = member['RemarkName'] 225 | if 'NickName' in member and member['NickName']: 226 | names['nickname'] = member['NickName'] 227 | if 'DisplayName' in member and member['DisplayName']: 228 | names['display_name'] = member['DisplayName'] 229 | return names 230 | return None 231 | 232 | def get_contact_info(self, uid): 233 | return self.account_info['normal_member'].get(uid) 234 | 235 | 236 | def get_group_member_info(self, uid): 237 | return self.account_info['group_member'].get(uid) 238 | 239 | def get_contact_name(self, uid): 240 | info = self.get_contact_info(uid) 241 | if info is None: 242 | return None 243 | info = info['info'] 244 | name = {} 245 | if 'RemarkName' in info and info['RemarkName']: 246 | name['remark_name'] = info['RemarkName'] 247 | if 'NickName' in info and info['NickName']: 248 | name['nickname'] = info['NickName'] 249 | if 'DisplayName' in info and info['DisplayName']: 250 | name['display_name'] = info['DisplayName'] 251 | if len(name) == 0: 252 | return None 253 | else: 254 | return name 255 | 256 | @staticmethod 257 | def get_contact_prefer_name(name): 258 | if name is None: 259 | return None 260 | if 'remark_name' in name: 261 | return name['remark_name'] 262 | if 'nickname' in name: 263 | return name['nickname'] 264 | if 'display_name' in name: 265 | return name['display_name'] 266 | return None 267 | 268 | @staticmethod 269 | def get_group_member_prefer_name(name): 270 | if name is None: 271 | return None 272 | if 'remark_name' in name: 273 | return name['remark_name'] 274 | if 'display_name' in name: 275 | return name['display_name'] 276 | if 'nickname' in name: 277 | return name['nickname'] 278 | return None 279 | 280 | def get_user_type(self, wx_user_id): 281 | """ 282 | 获取特定账号与自己的关系 283 | :param wx_user_id: 账号id: 284 | :return: 与当前账号的关系 285 | """ 286 | for account in self.contact_list: 287 | if wx_user_id == account['UserName']: 288 | return 'contact' 289 | for account in self.public_list: 290 | if wx_user_id == account['UserName']: 291 | return 'public' 292 | for account in self.special_list: 293 | if wx_user_id == account['UserName']: 294 | return 'special' 295 | for account in self.group_list: 296 | if wx_user_id == account['UserName']: 297 | return 'group' 298 | for group in self.group_members: 299 | for member in self.group_members[group]: 300 | if member['UserName'] == wx_user_id: 301 | return 'group_member' 302 | return 'unknown' 303 | 304 | def is_contact(self, uid): 305 | for account in self.contact_list: 306 | if uid == account['UserName']: 307 | return True 308 | return False 309 | 310 | def is_public(self, uid): 311 | for account in self.public_list: 312 | if uid == account['UserName']: 313 | return True 314 | return False 315 | 316 | def is_special(self, uid): 317 | for account in self.special_list: 318 | if uid == account['UserName']: 319 | return True 320 | return False 321 | 322 | def handle_msg_all(self, msg): 323 | """ 324 | 处理所有消息,请子类化后覆盖此函数 325 | msg: 326 | msg_id -> 消息id 327 | msg_type_id -> 消息类型id 328 | user -> 发送消息的账号id 329 | content -> 消息内容 330 | :param msg: 收到的消息 331 | """ 332 | pass 333 | 334 | @staticmethod 335 | def proc_at_info(msg): 336 | if not msg: 337 | return '', [] 338 | segs = msg.split(u'\u2005') 339 | str_msg_all = '' 340 | str_msg = '' 341 | infos = [] 342 | if len(segs) > 1: 343 | for i in range(0, len(segs) - 1): 344 | segs[i] += u'\u2005' 345 | pm = re.search(u'@.*\u2005', segs[i]).group() 346 | if pm: 347 | name = pm[1:-1] 348 | string = segs[i].replace(pm, '') 349 | str_msg_all += string + '@' + name + ' ' 350 | str_msg += string 351 | if string: 352 | infos.append({'type': 'str', 'value': string}) 353 | infos.append({'type': 'at', 'value': name}) 354 | else: 355 | infos.append({'type': 'str', 'value': segs[i]}) 356 | str_msg_all += segs[i] 357 | str_msg += segs[i] 358 | str_msg_all += segs[-1] 359 | str_msg += segs[-1] 360 | infos.append({'type': 'str', 'value': segs[-1]}) 361 | else: 362 | infos.append({'type': 'str', 'value': segs[-1]}) 363 | str_msg_all = msg 364 | str_msg = msg 365 | return str_msg_all.replace(u'\u2005', ''), str_msg.replace(u'\u2005', ''), infos 366 | 367 | def extract_msg_content(self, msg_type_id, msg): 368 | """ 369 | content_type_id: 370 | 0 -> Text 371 | 1 -> Location 372 | 3 -> Image 373 | 4 -> Voice 374 | 5 -> Recommend 375 | 6 -> Animation 376 | 7 -> Share 377 | 8 -> Video 378 | 9 -> VideoCall 379 | 10 -> Redraw 380 | 11 -> Empty 381 | 99 -> Unknown 382 | :param msg_type_id: 消息类型id 383 | :param msg: 消息结构体 384 | :return: 解析的消息 385 | """ 386 | mtype = msg['MsgType'] 387 | content = HTMLParser.HTMLParser().unescape(msg['Content']) 388 | msg_id = msg['MsgId'] 389 | 390 | msg_content = {} 391 | if msg_type_id == 0: 392 | return {'type': 11, 'data': ''} 393 | elif msg_type_id == 2: # File Helper 394 | return {'type': 0, 'data': content.replace('
', '\n')} 395 | elif msg_type_id == 3: # 群聊 396 | sp = content.find('
') 397 | uid = content[:sp] 398 | content = content[sp:] 399 | content = content.replace('
', '') 400 | uid = uid[:-1] 401 | name = self.get_contact_prefer_name(self.get_contact_name(uid)) 402 | if not name: 403 | name = self.get_group_member_prefer_name(self.get_group_member_name(msg['FromUserName'], uid)) 404 | if not name: 405 | name = 'unknown' 406 | msg_content['user'] = {'id': uid, 'name': name} 407 | else: # Self, Contact, Special, Public, Unknown 408 | pass 409 | 410 | msg_prefix = (msg_content['user']['name'] + ':') if 'user' in msg_content else '' 411 | 412 | if mtype == 1: 413 | if content.find('http://weixin.qq.com/cgi-bin/redirectforward?args=') != -1: 414 | r = self.session.get(content) 415 | r.encoding = 'gbk' 416 | data = r.text 417 | pos = self.search_content('title', data, 'xml') 418 | msg_content['type'] = 1 419 | msg_content['data'] = pos 420 | msg_content['detail'] = data 421 | if self.DEBUG: 422 | print ' %s[Location] %s ' % (msg_prefix, pos) 423 | else: 424 | msg_content['type'] = 0 425 | if msg_type_id == 3 or (msg_type_id == 1 and msg['ToUserName'][:2] == '@@'): # Group text message 426 | msg_infos = self.proc_at_info(content) 427 | str_msg_all = msg_infos[0] 428 | str_msg = msg_infos[1] 429 | detail = msg_infos[2] 430 | msg_content['data'] = str_msg_all 431 | msg_content['detail'] = detail 432 | msg_content['desc'] = str_msg 433 | else: 434 | msg_content['data'] = content 435 | if self.DEBUG: 436 | try: 437 | print ' %s[Text] %s' % (msg_prefix, msg_content['data']) 438 | except UnicodeEncodeError: 439 | print ' %s[Text] (illegal text).' % msg_prefix 440 | elif mtype == 3: 441 | msg_content['type'] = 3 442 | msg_content['data'] = self.get_msg_img_url(msg_id) 443 | msg_content['img'] = self.session.get(msg_content['data']).content.encode('hex') 444 | if self.DEBUG: 445 | image = self.get_msg_img(msg_id) 446 | print ' %s[Image] %s' % (msg_prefix, image) 447 | elif mtype == 34: 448 | msg_content['type'] = 4 449 | msg_content['data'] = self.get_voice_url(msg_id) 450 | msg_content['voice'] = self.session.get(msg_content['data']).content.encode('hex') 451 | if self.DEBUG: 452 | voice = self.get_voice(msg_id) 453 | print ' %s[Voice] %s' % (msg_prefix, voice) 454 | elif mtype == 37: 455 | msg_content['type'] = 37 456 | msg_content['data'] = msg['RecommendInfo'] 457 | if self.DEBUG: 458 | print ' %s[useradd] %s' % (msg_prefix,msg['RecommendInfo']['NickName']) 459 | elif mtype == 42: 460 | msg_content['type'] = 5 461 | info = msg['RecommendInfo'] 462 | msg_content['data'] = {'nickname': info['NickName'], 463 | 'alias': info['Alias'], 464 | 'province': info['Province'], 465 | 'city': info['City'], 466 | 'gender': ['unknown', 'male', 'female'][info['Sex']]} 467 | if self.DEBUG: 468 | print ' %s[Recommend]' % msg_prefix 469 | print ' -----------------------------' 470 | print ' | NickName: %s' % info['NickName'] 471 | print ' | Alias: %s' % info['Alias'] 472 | print ' | Local: %s %s' % (info['Province'], info['City']) 473 | print ' | Gender: %s' % ['unknown', 'male', 'female'][info['Sex']] 474 | print ' -----------------------------' 475 | elif mtype == 47: 476 | msg_content['type'] = 6 477 | msg_content['data'] = self.search_content('cdnurl', content) 478 | if self.DEBUG: 479 | print ' %s[Animation] %s' % (msg_prefix, msg_content['data']) 480 | elif mtype == 49: 481 | msg_content['type'] = 7 482 | if msg['AppMsgType'] == 3: 483 | app_msg_type = 'music' 484 | elif msg['AppMsgType'] == 5: 485 | app_msg_type = 'link' 486 | elif msg['AppMsgType'] == 7: 487 | app_msg_type = 'weibo' 488 | else: 489 | app_msg_type = 'unknown' 490 | msg_content['data'] = {'type': app_msg_type, 491 | 'title': msg['FileName'], 492 | 'desc': self.search_content('des', content, 'xml'), 493 | 'url': msg['Url'], 494 | 'from': self.search_content('appname', content, 'xml'), 495 | 'content': msg.get('Content') # 有的公众号会发一次性3 4条链接一个大图,如果只url那只能获取第一条,content里面有所有的链接 496 | } 497 | if self.DEBUG: 498 | print ' %s[Share] %s' % (msg_prefix, app_msg_type) 499 | print ' --------------------------' 500 | print ' | title: %s' % msg['FileName'] 501 | print ' | desc: %s' % self.search_content('des', content, 'xml') 502 | print ' | link: %s' % msg['Url'] 503 | print ' | from: %s' % self.search_content('appname', content, 'xml') 504 | print ' | content: %s' % (msg.get('content')[:20] if msg.get('content') else "unknown") 505 | print ' --------------------------' 506 | 507 | elif mtype == 62: 508 | msg_content['type'] = 8 509 | msg_content['data'] = content 510 | if self.DEBUG: 511 | print ' %s[Video] Please check on mobiles' % msg_prefix 512 | elif mtype == 53: 513 | msg_content['type'] = 9 514 | msg_content['data'] = content 515 | if self.DEBUG: 516 | print ' %s[Video Call]' % msg_prefix 517 | elif mtype == 10002: 518 | msg_content['type'] = 10 519 | msg_content['data'] = content 520 | if self.DEBUG: 521 | print ' %s[Redraw]' % msg_prefix 522 | elif mtype == 10000: # unknown, maybe red packet, or group invite 523 | msg_content['type'] = 12 524 | msg_content['data'] = msg['Content'] 525 | if self.DEBUG: 526 | print ' [Unknown]' 527 | else: 528 | msg_content['type'] = 99 529 | msg_content['data'] = content 530 | if self.DEBUG: 531 | print ' %s[Unknown]' % msg_prefix 532 | return msg_content 533 | 534 | def handle_msg(self, r): 535 | """ 536 | 处理原始微信消息的内部函数 537 | msg_type_id: 538 | 0 -> Init 539 | 1 -> Self 540 | 2 -> FileHelper 541 | 3 -> Group 542 | 4 -> Contact 543 | 5 -> Public 544 | 6 -> Special 545 | 99 -> Unknown 546 | :param r: 原始微信消息 547 | """ 548 | for msg in r['AddMsgList']: 549 | user = {'id': msg['FromUserName'], 'name': 'unknown'} 550 | if msg['MsgType'] == 51: # init message 551 | msg_type_id = 0 552 | user['name'] = 'system' 553 | elif msg['MsgType'] == 37: # friend request 554 | msg_type_id = 37 555 | pass 556 | # content = msg['Content'] 557 | # username = content[content.index('fromusername='): content.index('encryptusername')] 558 | # username = username[username.index('"') + 1: username.rindex('"')] 559 | # print u'[Friend Request]' 560 | # print u' Nickname:' + msg['RecommendInfo']['NickName'] 561 | # print u' 附加消息:'+msg['RecommendInfo']['Content'] 562 | # # print u'Ticket:'+msg['RecommendInfo']['Ticket'] # Ticket添加好友时要用 563 | # print u' 微信号:'+username #未设置微信号的 腾讯会自动生成一段微信ID 但是无法通过搜索 搜索到此人 564 | elif msg['FromUserName'] == self.my_account['UserName']: # Self 565 | msg_type_id = 1 566 | user['name'] = 'self' 567 | elif msg['ToUserName'] == 'filehelper': # File Helper 568 | msg_type_id = 2 569 | user['name'] = 'file_helper' 570 | elif msg['FromUserName'][:2] == '@@': # Group 571 | msg_type_id = 3 572 | user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id'])) 573 | elif self.is_contact(msg['FromUserName']): # Contact 574 | msg_type_id = 4 575 | user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id'])) 576 | elif self.is_public(msg['FromUserName']): # Public 577 | msg_type_id = 5 578 | user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id'])) 579 | elif self.is_special(msg['FromUserName']): # Special 580 | msg_type_id = 6 581 | user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id'])) 582 | else: 583 | msg_type_id = 99 584 | user['name'] = 'unknown' 585 | if not user['name']: 586 | user['name'] = 'unknown' 587 | user['name'] = HTMLParser.HTMLParser().unescape(user['name']) 588 | 589 | if self.DEBUG and msg_type_id != 0: 590 | print u'[MSG] %s:' % user['name'] 591 | content = self.extract_msg_content(msg_type_id, msg) 592 | message = {'msg_type_id': msg_type_id, 593 | 'msg_id': msg['MsgId'], 594 | 'content': content, 595 | 'to_user_id': msg['ToUserName'], 596 | 'user': user} 597 | self.handle_msg_all(message) 598 | 599 | def schedule(self): 600 | """ 601 | 做任务型事情的函数,如果需要,可以在子类中覆盖此函数 602 | 此函数在处理消息的间隙被调用,请不要长时间阻塞此函数 603 | """ 604 | pass 605 | 606 | def proc_msg(self): 607 | self.test_sync_check() 608 | while True: 609 | check_time = time.time() 610 | try: 611 | [retcode, selector] = self.sync_check() 612 | # print '[DEBUG] sync_check:', retcode, selector 613 | if retcode == '1100': # 从微信客户端上登出 614 | break 615 | elif retcode == '1101': # 从其它设备上登了网页微信 616 | break 617 | elif retcode == '0': 618 | if selector == '2': # 有新消息 619 | r = self.sync() 620 | if r is not None: 621 | self.handle_msg(r) 622 | elif selector == '3': # 未知 623 | r = self.sync() 624 | if r is not None: 625 | self.handle_msg(r) 626 | elif selector == '4': # 通讯录更新 627 | r = self.sync() 628 | if r is not None: 629 | self.get_contact() 630 | elif selector == '6': # 可能是红包 631 | r = self.sync() 632 | if r is not None: 633 | self.handle_msg(r) 634 | elif selector == '7': # 在手机上操作了微信 635 | r = self.sync() 636 | if r is not None: 637 | self.handle_msg(r) 638 | elif selector == '0': # 无事件 639 | pass 640 | else: 641 | print '[DEBUG] sync_check:', retcode, selector 642 | r = self.sync() 643 | if r is not None: 644 | self.handle_msg(r) 645 | else: 646 | print '[DEBUG] sync_check:', retcode, selector 647 | time.sleep(10) 648 | self.schedule() 649 | except: 650 | print '[ERROR] Except in proc_msg' 651 | print format_exc() 652 | check_time = time.time() - check_time 653 | if check_time < 0.8: 654 | time.sleep(1 - check_time) 655 | 656 | def apply_useradd_requests(self,RecommendInfo): 657 | url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN' 658 | params = { 659 | "BaseRequest": self.base_request, 660 | "Opcode": 3, 661 | "VerifyUserListSize": 1, 662 | "VerifyUserList": [ 663 | { 664 | "Value": RecommendInfo['UserName'], 665 | "VerifyUserTicket": RecommendInfo['Ticket'] } 666 | ], 667 | "VerifyContent": "", 668 | "SceneListCount": 1, 669 | "SceneList": [ 670 | 33 671 | ], 672 | "skey": self.skey 673 | } 674 | headers = {'content-type': 'application/json; charset=UTF-8'} 675 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 676 | try: 677 | r = self.session.post(url, data=data, headers=headers) 678 | except (ConnectionError, ReadTimeout): 679 | return False 680 | dic = r.json() 681 | return dic['BaseResponse']['Ret'] == 0 682 | 683 | def add_groupuser_to_friend_by_uid(self,uid,VerifyContent): 684 | """ 685 | 主动向群内人员打招呼,提交添加好友请求 686 | uid-群内人员得uid VerifyContent-好友招呼内容 687 | 慎用此接口!封号后果自负!慎用此接口!封号后果自负!慎用此接口!封号后果自负! 688 | """ 689 | if self.is_contact(uid): 690 | return True 691 | url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN' 692 | params ={ 693 | "BaseRequest": self.base_request, 694 | "Opcode": 2, 695 | "VerifyUserListSize": 1, 696 | "VerifyUserList": [ 697 | { 698 | "Value": uid, 699 | "VerifyUserTicket": "" 700 | } 701 | ], 702 | "VerifyContent": VerifyContent, 703 | "SceneListCount": 1, 704 | "SceneList": [ 705 | 33 706 | ], 707 | "skey": self.skey 708 | } 709 | headers = {'content-type': 'application/json; charset=UTF-8'} 710 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 711 | try: 712 | r = self.session.post(url, data=data, headers=headers) 713 | except (ConnectionError, ReadTimeout): 714 | return False 715 | dic = r.json() 716 | return dic['BaseResponse']['Ret'] == 0 717 | 718 | def add_friend_to_group(self,uid,group_name): 719 | """ 720 | 将好友加入到群聊中 721 | """ 722 | gid = '' 723 | #通过群名获取群id,群没保存到通讯录中的话无法添加哦 724 | for group in self.group_list: 725 | if group['NickName'] == group_name: 726 | gid = group['UserName'] 727 | if gid == '': 728 | return False 729 | #通过群id判断uid是否在群中 730 | for user in self.group_members[gid]: 731 | if user['UserName'] == uid: 732 | #已经在群里面了,不用加了 733 | return True 734 | url = self.base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % self.pass_ticket 735 | params ={ 736 | "AddMemberList": uid, 737 | "ChatRoomName": gid, 738 | "BaseRequest": self.base_request 739 | } 740 | headers = {'content-type': 'application/json; charset=UTF-8'} 741 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 742 | try: 743 | r = self.session.post(url, data=data, headers=headers) 744 | except (ConnectionError, ReadTimeout): 745 | return False 746 | dic = r.json() 747 | return dic['BaseResponse']['Ret'] == 0 748 | 749 | def delete_user_from_group(self,uname,gid): 750 | """ 751 | 将群用户从群中剔除,只有群管理员有权限 752 | """ 753 | uid = "" 754 | for user in self.group_members[gid]: 755 | if user['NickName'] == uname: 756 | uid = user['UserName'] 757 | if uid == "": 758 | return False 759 | url = self.base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % self.pass_ticket 760 | params ={ 761 | "DelMemberList": uid, 762 | "ChatRoomName": gid, 763 | "BaseRequest": self.base_request 764 | } 765 | headers = {'content-type': 'application/json; charset=UTF-8'} 766 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 767 | try: 768 | r = self.session.post(url, data=data, headers=headers) 769 | except (ConnectionError, ReadTimeout): 770 | return False 771 | dic = r.json() 772 | return dic['BaseResponse']['Ret'] == 0 773 | 774 | def set_group_name(self,gid,gname): 775 | """ 776 | 设置群聊名称 777 | """ 778 | url = self.base_uri + '/webwxupdatechatroom?fun=modtopic&pass_ticket=%s' % self.pass_ticket 779 | params ={ 780 | "NewTopic": gname, 781 | "ChatRoomName": gid, 782 | "BaseRequest": self.base_request 783 | } 784 | headers = {'content-type': 'application/json; charset=UTF-8'} 785 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 786 | try: 787 | r = self.session.post(url, data=data, headers=headers) 788 | except (ConnectionError, ReadTimeout): 789 | return False 790 | dic = r.json() 791 | return dic['BaseResponse']['Ret'] == 0 792 | 793 | def send_msg_by_uid(self, word, dst='filehelper'): 794 | url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket 795 | msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '') 796 | word = self.to_unicode(word) 797 | params = { 798 | 'BaseRequest': self.base_request, 799 | 'Msg': { 800 | "Type": 1, 801 | "Content": word, 802 | "FromUserName": self.my_account['UserName'], 803 | "ToUserName": dst, 804 | "LocalID": msg_id, 805 | "ClientMsgId": msg_id 806 | } 807 | } 808 | headers = {'content-type': 'application/json; charset=UTF-8'} 809 | data = json.dumps(params, ensure_ascii=False).encode('utf8') 810 | try: 811 | r = self.session.post(url, data=data, headers=headers) 812 | except (ConnectionError, ReadTimeout): 813 | return False 814 | dic = r.json() 815 | return dic['BaseResponse']['Ret'] == 0 816 | 817 | def upload_media(self, fpath, is_img=False): 818 | if not os.path.exists(fpath): 819 | print '[ERROR] File not exists.' 820 | return None 821 | url_1 = 'https://file.'+self.base_host+'/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json' 822 | url_2 = 'https://file2.'+self.base_host+'/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json' 823 | flen = str(os.path.getsize(fpath)) 824 | ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream' 825 | files = { 826 | 'id': (None, 'WU_FILE_%s' % str(self.file_index)), 827 | 'name': (None, os.path.basename(fpath)), 828 | 'type': (None, ftype), 829 | 'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')), 830 | 'size': (None, flen), 831 | 'mediatype': (None, 'pic' if is_img else 'doc'), 832 | 'uploadmediarequest': (None, json.dumps({ 833 | 'BaseRequest': self.base_request, 834 | 'ClientMediaId': int(time.time()), 835 | 'TotalLen': flen, 836 | 'StartPos': 0, 837 | 'DataLen': flen, 838 | 'MediaType': 4, 839 | })), 840 | 'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']), 841 | 'pass_ticket': (None, self.pass_ticket), 842 | 'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]), 843 | } 844 | self.file_index += 1 845 | try: 846 | r = self.session.post(url_1, files=files) 847 | if json.loads(r.text)['BaseResponse']['Ret'] != 0: 848 | # 当file返回值不为0时则为上传失败,尝试第二服务器上传 849 | r = self.session.post(url_2, files=files) 850 | if json.loads(r.text)['BaseResponse']['Ret'] != 0: 851 | print '[ERROR] Upload media failure.' 852 | return None 853 | mid = json.loads(r.text)['MediaId'] 854 | return mid 855 | except Exception,e: 856 | return None 857 | 858 | def send_file_msg_by_uid(self, fpath, uid): 859 | mid = self.upload_media(fpath) 860 | if mid is None or not mid: 861 | return False 862 | url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket 863 | msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '') 864 | data = { 865 | 'BaseRequest': self.base_request, 866 | 'Msg': { 867 | 'Type': 6, 868 | 'Content': ("%s6%s%s%s" % (os.path.basename(fpath).encode('utf-8'), str(os.path.getsize(fpath)), mid, fpath.split('.')[-1])).encode('utf8'), 869 | 'FromUserName': self.my_account['UserName'], 870 | 'ToUserName': uid, 871 | 'LocalID': msg_id, 872 | 'ClientMsgId': msg_id, }, } 873 | try: 874 | r = self.session.post(url, data=json.dumps(data)) 875 | res = json.loads(r.text) 876 | if res['BaseResponse']['Ret'] == 0: 877 | return True 878 | else: 879 | return False 880 | except Exception,e: 881 | return False 882 | 883 | def send_img_msg_by_uid(self, fpath, uid): 884 | mid = self.upload_media(fpath, is_img=True) 885 | if mid is None: 886 | return False 887 | url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json' 888 | data = { 889 | 'BaseRequest': self.base_request, 890 | 'Msg': { 891 | 'Type': 3, 892 | 'MediaId': mid, 893 | 'FromUserName': self.my_account['UserName'], 894 | 'ToUserName': uid, 895 | 'LocalID': str(time.time() * 1e7), 896 | 'ClientMsgId': str(time.time() * 1e7), }, } 897 | if fpath[-4:] == '.gif': 898 | url = self.base_uri + '/webwxsendemoticon?fun=sys' 899 | data['Msg']['Type'] = 47 900 | data['Msg']['EmojiFlag'] = 2 901 | try: 902 | r = self.session.post(url, data=json.dumps(data)) 903 | res = json.loads(r.text) 904 | if res['BaseResponse']['Ret'] == 0: 905 | return True 906 | else: 907 | return False 908 | except Exception,e: 909 | return False 910 | 911 | def get_user_id(self, name): 912 | if name == '': 913 | return None 914 | name = self.to_unicode(name) 915 | for contact in self.contact_list: 916 | if 'RemarkName' in contact and contact['RemarkName'] == name: 917 | return contact['UserName'] 918 | elif 'NickName' in contact and contact['NickName'] == name: 919 | return contact['UserName'] 920 | elif 'DisplayName' in contact and contact['DisplayName'] == name: 921 | return contact['UserName'] 922 | for group in self.group_list: 923 | if 'RemarkName' in group and group['RemarkName'] == name: 924 | return group['UserName'] 925 | if 'NickName' in group and group['NickName'] == name: 926 | return group['UserName'] 927 | if 'DisplayName' in group and group['DisplayName'] == name: 928 | return group['UserName'] 929 | 930 | return '' 931 | 932 | def send_msg(self, name, word, isfile=False): 933 | uid = self.get_user_id(name) 934 | if uid is not None: 935 | if isfile: 936 | with open(word, 'r') as f: 937 | result = True 938 | for line in f.readlines(): 939 | line = line.replace('\n', '') 940 | print '-> ' + name + ': ' + line 941 | if self.send_msg_by_uid(line, uid): 942 | pass 943 | else: 944 | result = False 945 | time.sleep(1) 946 | return result 947 | else: 948 | word = self.to_unicode(word) 949 | if self.send_msg_by_uid(word, uid): 950 | return True 951 | else: 952 | return False 953 | else: 954 | if self.DEBUG: 955 | print '[ERROR] This user does not exist .' 956 | return True 957 | 958 | @staticmethod 959 | def search_content(key, content, fmat='attr'): 960 | if fmat == 'attr': 961 | pm = re.search(key + '\s?=\s?"([^"<]+)"', content) 962 | if pm: 963 | return pm.group(1) 964 | elif fmat == 'xml': 965 | pm = re.search('<{0}>([^<]+)'.format(key), content) 966 | if pm: 967 | return pm.group(1) 968 | return 'unknown' 969 | 970 | def run(self): 971 | self.get_uuid() 972 | self.gen_qr_code(os.path.join(self.temp_pwd,'wxqr.png')) 973 | print '[INFO] Please use WeChat to scan the QR code .' 974 | 975 | result = self.wait4login() 976 | if result != SUCCESS: 977 | print '[ERROR] Web WeChat login failed. failed code=%s' % (result,) 978 | return 979 | 980 | if self.login(): 981 | print '[INFO] Web WeChat login succeed .' 982 | else: 983 | print '[ERROR] Web WeChat login failed .' 984 | return 985 | 986 | if self.init(): 987 | print '[INFO] Web WeChat init succeed .' 988 | else: 989 | print '[INFO] Web WeChat init failed' 990 | return 991 | self.status_notify() 992 | self.get_contact() 993 | print '[INFO] Get %d contacts' % len(self.contact_list) 994 | print '[INFO] Start to process messages .' 995 | self.proc_msg() 996 | 997 | def get_uuid(self): 998 | url = 'https://login.weixin.qq.com/jslogin' 999 | params = { 1000 | 'appid': 'wx782c26e4c19acffb', 1001 | 'fun': 'new', 1002 | 'lang': 'zh_CN', 1003 | '_': int(time.time()) * 1000 + random.randint(1, 999), 1004 | } 1005 | r = self.session.get(url, params=params) 1006 | r.encoding = 'utf-8' 1007 | data = r.text 1008 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' 1009 | pm = re.search(regx, data) 1010 | if pm: 1011 | code = pm.group(1) 1012 | self.uuid = pm.group(2) 1013 | return code == '200' 1014 | return False 1015 | 1016 | def gen_qr_code(self, qr_file_path): 1017 | string = 'https://login.weixin.qq.com/l/' + self.uuid 1018 | qr = pyqrcode.create(string) 1019 | if self.conf['qr'] == 'png': 1020 | qr.png(qr_file_path, scale=8) 1021 | show_image(qr_file_path) 1022 | # img = Image.open(qr_file_path) 1023 | # img.show() 1024 | elif self.conf['qr'] == 'tty': 1025 | print(qr.terminal(quiet_zone=1)) 1026 | 1027 | def do_request(self, url): 1028 | r = self.session.get(url) 1029 | r.encoding = 'utf-8' 1030 | data = r.text 1031 | param = re.search(r'window.code=(\d+);', data) 1032 | code = param.group(1) 1033 | return code, data 1034 | 1035 | def wait4login(self): 1036 | """ 1037 | http comet: 1038 | tip=1, 等待用户扫描二维码, 1039 | 201: scaned 1040 | 408: timeout 1041 | tip=0, 等待用户确认登录, 1042 | 200: confirmed 1043 | """ 1044 | LOGIN_TEMPLATE = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' 1045 | tip = 1 1046 | 1047 | try_later_secs = 1 1048 | MAX_RETRY_TIMES = 10 1049 | 1050 | code = UNKONWN 1051 | 1052 | retry_time = MAX_RETRY_TIMES 1053 | while retry_time > 0: 1054 | url = LOGIN_TEMPLATE % (tip, self.uuid, int(time.time())) 1055 | code, data = self.do_request(url) 1056 | if code == SCANED: 1057 | print '[INFO] Please confirm to login .' 1058 | tip = 0 1059 | elif code == SUCCESS: # 确认登录成功 1060 | param = re.search(r'window.redirect_uri="(\S+?)";', data) 1061 | redirect_uri = param.group(1) + '&fun=new' 1062 | self.redirect_uri = redirect_uri 1063 | self.base_uri = redirect_uri[:redirect_uri.rfind('/')] 1064 | temp_host = self.base_uri[8:] 1065 | self.base_host = temp_host[:temp_host.find("/")] 1066 | return code 1067 | elif code == TIMEOUT: 1068 | print '[ERROR] WeChat login timeout. retry in %s secs later...' % (try_later_secs,) 1069 | 1070 | tip = 1 # 重置 1071 | retry_time -= 1 1072 | time.sleep(try_later_secs) 1073 | else: 1074 | print ('[ERROR] WeChat login exception return_code=%s. retry in %s secs later...' % 1075 | (code, try_later_secs)) 1076 | tip = 1 1077 | retry_time -= 1 1078 | time.sleep(try_later_secs) 1079 | 1080 | return code 1081 | 1082 | def login(self): 1083 | if len(self.redirect_uri) < 4: 1084 | print '[ERROR] Login failed due to network problem, please try again.' 1085 | return False 1086 | r = self.session.get(self.redirect_uri) 1087 | r.encoding = 'utf-8' 1088 | data = r.text 1089 | doc = xml.dom.minidom.parseString(data) 1090 | root = doc.documentElement 1091 | 1092 | for node in root.childNodes: 1093 | if node.nodeName == 'skey': 1094 | self.skey = node.childNodes[0].data 1095 | elif node.nodeName == 'wxsid': 1096 | self.sid = node.childNodes[0].data 1097 | elif node.nodeName == 'wxuin': 1098 | self.uin = node.childNodes[0].data 1099 | elif node.nodeName == 'pass_ticket': 1100 | self.pass_ticket = node.childNodes[0].data 1101 | 1102 | if '' in (self.skey, self.sid, self.uin, self.pass_ticket): 1103 | return False 1104 | 1105 | self.base_request = { 1106 | 'Uin': self.uin, 1107 | 'Sid': self.sid, 1108 | 'Skey': self.skey, 1109 | 'DeviceID': self.device_id, 1110 | } 1111 | return True 1112 | 1113 | def init(self): 1114 | url = self.base_uri + '/webwxinit?r=%i&lang=en_US&pass_ticket=%s' % (int(time.time()), self.pass_ticket) 1115 | params = { 1116 | 'BaseRequest': self.base_request 1117 | } 1118 | r = self.session.post(url, data=json.dumps(params)) 1119 | r.encoding = 'utf-8' 1120 | dic = json.loads(r.text) 1121 | self.sync_key = dic['SyncKey'] 1122 | self.my_account = dic['User'] 1123 | self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val']) 1124 | for keyVal in self.sync_key['List']]) 1125 | return dic['BaseResponse']['Ret'] == 0 1126 | 1127 | def status_notify(self): 1128 | url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket 1129 | self.base_request['Uin'] = int(self.base_request['Uin']) 1130 | params = { 1131 | 'BaseRequest': self.base_request, 1132 | "Code": 3, 1133 | "FromUserName": self.my_account['UserName'], 1134 | "ToUserName": self.my_account['UserName'], 1135 | "ClientMsgId": int(time.time()) 1136 | } 1137 | r = self.session.post(url, data=json.dumps(params)) 1138 | r.encoding = 'utf-8' 1139 | dic = json.loads(r.text) 1140 | return dic['BaseResponse']['Ret'] == 0 1141 | 1142 | def test_sync_check(self): 1143 | for host1 in ['webpush.', 'webpush2.']: 1144 | self.sync_host = host1+self.base_host 1145 | try: 1146 | retcode = self.sync_check()[0] 1147 | except: 1148 | retcode = -1 1149 | if retcode == '0': 1150 | return True 1151 | return False 1152 | 1153 | def sync_check(self): 1154 | params = { 1155 | 'r': int(time.time()), 1156 | 'sid': self.sid, 1157 | 'uin': self.uin, 1158 | 'skey': self.skey, 1159 | 'deviceid': self.device_id, 1160 | 'synckey': self.sync_key_str, 1161 | '_': int(time.time()), 1162 | } 1163 | url = 'https://' + self.sync_host + '/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params) 1164 | try: 1165 | r = self.session.get(url, timeout=60) 1166 | r.encoding = 'utf-8' 1167 | data = r.text 1168 | pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data) 1169 | retcode = pm.group(1) 1170 | selector = pm.group(2) 1171 | return [retcode, selector] 1172 | except: 1173 | return [-1, -1] 1174 | 1175 | def sync(self): 1176 | url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \ 1177 | % (self.sid, self.skey, self.pass_ticket) 1178 | params = { 1179 | 'BaseRequest': self.base_request, 1180 | 'SyncKey': self.sync_key, 1181 | 'rr': ~int(time.time()) 1182 | } 1183 | try: 1184 | r = self.session.post(url, data=json.dumps(params), timeout=60) 1185 | r.encoding = 'utf-8' 1186 | dic = json.loads(r.text) 1187 | if dic['BaseResponse']['Ret'] == 0: 1188 | self.sync_key = dic['SyncKey'] 1189 | self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val']) 1190 | for keyVal in self.sync_key['List']]) 1191 | return dic 1192 | except: 1193 | return None 1194 | 1195 | def get_icon(self, uid, gid=None): 1196 | """ 1197 | 获取联系人或者群聊成员头像 1198 | :param uid: 联系人id 1199 | :param gid: 群id,如果为非None获取群中成员头像,如果为None则获取联系人头像 1200 | """ 1201 | if gid is None: 1202 | url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey) 1203 | else: 1204 | url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % ( 1205 | uid, self.skey, self.encry_chat_room_id_list[gid]) 1206 | r = self.session.get(url) 1207 | data = r.content 1208 | fn = 'icon_' + uid + '.jpg' 1209 | with open(os.path.join(self.temp_pwd,fn), 'wb') as f: 1210 | f.write(data) 1211 | return fn 1212 | 1213 | def get_head_img(self, uid): 1214 | """ 1215 | 获取群头像 1216 | :param uid: 群uid 1217 | """ 1218 | url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey) 1219 | r = self.session.get(url) 1220 | data = r.content 1221 | fn = 'head_' + uid + '.jpg' 1222 | with open(os.path.join(self.temp_pwd,fn), 'wb') as f: 1223 | f.write(data) 1224 | return fn 1225 | 1226 | def get_msg_img_url(self, msgid): 1227 | return self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey) 1228 | 1229 | def get_msg_img(self, msgid): 1230 | """ 1231 | 获取图片消息,下载图片到本地 1232 | :param msgid: 消息id 1233 | :return: 保存的本地图片文件路径 1234 | """ 1235 | url = self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey) 1236 | r = self.session.get(url) 1237 | data = r.content 1238 | fn = 'img_' + msgid + '.jpg' 1239 | with open(os.path.join(self.temp_pwd,fn), 'wb') as f: 1240 | f.write(data) 1241 | return fn 1242 | 1243 | def get_voice_url(self, msgid): 1244 | return self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey) 1245 | 1246 | def get_voice(self, msgid): 1247 | """ 1248 | 获取语音消息,下载语音到本地 1249 | :param msgid: 语音消息id 1250 | :return: 保存的本地语音文件路径 1251 | """ 1252 | url = self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey) 1253 | r = self.session.get(url) 1254 | data = r.content 1255 | fn = 'voice_' + msgid + '.mp3' 1256 | with open(os.path.join(self.temp_pwd,fn), 'wb') as f: 1257 | f.write(data) 1258 | return fn 1259 | def set_remarkname(self,uid,remarkname):#设置联系人的备注名 1260 | url = self.base_uri + '/webwxoplog?lang=zh_CN&pass_ticket=%s' \ 1261 | % (self.pass_ticket) 1262 | remarkname = self.to_unicode(remarkname) 1263 | params = { 1264 | 'BaseRequest': self.base_request, 1265 | 'CmdId': 2, 1266 | 'RemarkName': remarkname, 1267 | 'UserName': uid 1268 | } 1269 | try: 1270 | r = self.session.post(url, data=json.dumps(params), timeout=60) 1271 | r.encoding = 'utf-8' 1272 | dic = json.loads(r.text) 1273 | return dic['BaseResponse']['ErrMsg'] 1274 | except: 1275 | return None 1276 | -------------------------------------------------------------------------------- /微信淘宝推广机器人v0.3.12_sign_win.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiehe/wechat-coupon-robot/57f85be01d37480164f915cc2ca846eb3a82b004/微信淘宝推广机器人v0.3.12_sign_win.zip --------------------------------------------------------------------------------