├── .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}>([^<]+){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
--------------------------------------------------------------------------------