├── tmp └── tmp.txt ├── doc ├── BUGLIST.md ├── TODOLIST.md ├── wx │ ├── msg_type.py │ ├── gzh_API.md │ └── msg.js └── CHANGELOG.md ├── static └── img │ └── wx │ └── zsxq.jpeg ├── core ├── mixin.py ├── __init__.py ├── base.py ├── friend.py └── filehelper.py ├── test ├── wx │ ├── __init__.py │ └── test_filehelper.py └── __init__.py ├── etc ├── __init__.py ├── wx_cfg.py └── base_cfg.py ├── utility ├── __init__.py └── misc.py ├── app ├── __init__.py ├── main.py └── gzh.py ├── requirements.txt ├── README.md └── .gitignore /tmp/tmp.txt: -------------------------------------------------------------------------------- 1 | tmp -------------------------------------------------------------------------------- /doc/BUGLIST.md: -------------------------------------------------------------------------------- 1 | - BUG-0001 2 | ``` 3 | Fix path import error. 4 | ``` -------------------------------------------------------------------------------- /static/img/wx/zsxq.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylinyang0/wxRobot/HEAD/static/img/wx/zsxq.jpeg -------------------------------------------------------------------------------- /core/mixin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/24/18 4 | 5 | __author__ = 'MiracleYoung' 6 | -------------------------------------------------------------------------------- /doc/TODOLIST.md: -------------------------------------------------------------------------------- 1 | - friends 2 | - 自动打标签 3 | - 邀请入群(目前受限制于微信api) 4 | - filehelper 5 | - 群发文章,自动识别链接并转化成文章 6 | - 菜单栏 -------------------------------------------------------------------------------- /test/wx/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/18/18 4 | 5 | __author__ = 'MiracleYoung' -------------------------------------------------------------------------------- /etc/__init__.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/env python 2 | # Created by Miracle at 9/11/18 3 | 4 | 5 | from .base_cfg import * 6 | from .wx_cfg import * -------------------------------------------------------------------------------- /utility/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/24/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | from .misc import * -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/7/9 下午2:37 4 | # @Author : MiracleYoung 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/4/8 上午9:11 4 | # @Author : MiracleYoung 5 | # @File : __init__.py.py 6 | 7 | 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.8.24 2 | chardet==3.0.4 3 | future==0.16.0 4 | idna==2.7 5 | itchat==1.2.32 6 | pypng==0.0.18 7 | PyQRCode==1.2.1 8 | requests==2.19.1 9 | urllib3==1.23 10 | wxpy==0.3.9.8 11 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/24/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | from .filehelper import * 8 | from .friend import * 9 | from .mixin import * 10 | -------------------------------------------------------------------------------- /doc/wx/msg_type.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/4/8 上午9:44 4 | # @Author : MiracleYoung 5 | # @File : msg_type.py 6 | 7 | MSG_TYPE = { 8 | 'TEXT': 1, 9 | 'PICTURE': 3 10 | } -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v1.0.0 release 2 | By 2018.10.24 3 | - 好友 4 | - 自动添加好友 5 | - 自动给好友设置别名 6 | - 关键字回复 7 | - filehelper 8 | - 群发消息(群根据有限制、无限制、禁止分3类) 9 | 10 | ### v1.0.2 release 11 | By 2018.10.25 12 | - Fix Bug-0001. -------------------------------------------------------------------------------- /etc/wx_cfg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/19/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | import itchat 8 | 9 | UNLIMIT_GROUP = [] 10 | LIMIT_GROUP = [ 11 | '猿媛牧场1群', '猿媛牧场2群', '淼淼之森', '格姗知识圈群', 'leoay社区', '限时推文', '限推' 12 | ] 13 | RESTRICT_GROUP = [ 14 | '禁推文', '禁止推文' 15 | ] 16 | 17 | FRIEND_ASK_KW = [ 18 | '合作', '商务', 19 | ] 20 | 21 | AD_KW = ['广告'] 22 | PT_KW = ['项目', '有偿', '兼职'] 23 | 24 | instance = itchat.new_instance() 25 | is_open = False 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # wxRobot :boom: 4 | 微信自助机器人。 5 | 6 | 最初的目的是为了帮助我更好的运营公众号所开发的。 7 | 8 | 能够帮助用户自动完成一些日常操作,比如:群发消息,群发文章,自动回复,自动添加好友等。 9 | 10 | ## Installing 11 | 1. 安装所需包:`pip install -r requirments.txt` 12 | 13 | ## Usage 14 | 1. `python app/main.py` 15 | 16 | ## Features 17 | 目前支持的特性有: 18 | - 好友 19 | - 自动添加好友 20 | - 自动给好友设置别名 21 | - 关键字回复 22 | - filehelper 23 | - 群发消息(群根据有限制、无限制、禁止分3类) 24 | 25 | [TODOLIST](doc/TODOLIST.md) 26 | 27 | [CHANGELOG](doc/CHANGELOG.md) 28 | 29 | [CHANGELOG](doc/BUGLIST.md) 30 | 31 | ## Contributing 32 | - Miracle: miracleyoung0723@gmail.com 33 | 34 | -------------------------------------------------------------------------------- /core/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/18/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | __all__ = ['BaseHandle'] 8 | 9 | 10 | class BaseHandle: 11 | 12 | def __init__(self): 13 | ''' 14 | _meta = { 15 | 'obj':{ # 消息发送对象 16 | 'ul': [], # unlimit group 17 | 'l': [], # limit group 18 | 'r': [] # restrict 19 | }, 20 | 'reply':{ 21 | 'text': '', 22 | 'article': '', 23 | } 24 | } 25 | ''' 26 | self._usage = '' 27 | self._meta = {} 28 | self.current_cmd = None 29 | 30 | @property 31 | def usage(self): 32 | return self._usage 33 | 34 | @property 35 | def meta(self): 36 | return self._meta 37 | -------------------------------------------------------------------------------- /core/friend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/24/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | from core.base import BaseHandle 8 | from etc import FRIEND_ASK_KW 9 | 10 | __all__ = ['friend'] 11 | 12 | 13 | class Friend(BaseHandle): 14 | _usage = ''' 15 | 16 | ''' 17 | 18 | def __init__(self): 19 | super().__init__() 20 | self._meta = { 21 | 'extra': { 22 | 'ask_kw': FRIEND_ASK_KW, 23 | 'welcome': "由于人数已满100,回复:“技术群”,拉你入群。\n 知识星球内有「Python实战」、「大航海计划」、「大厂内推」、「读书、技术汇」等。\n在这个星球能够得到的,不只是关于Python,圈子、人脉、资源、学习氛围、眼界都是比技术更值得去借鉴的东西。\n如果想要加入知识星球的话,可以回复“知识星球”" 24 | }, 25 | } 26 | 27 | def is_biz(self, msg): 28 | for kw in self._meta['extra']['ask_kw']: 29 | if kw in msg: 30 | return True 31 | return False 32 | 33 | 34 | friend = Friend() 35 | -------------------------------------------------------------------------------- /utility/misc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/24/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | import functools 8 | 9 | from etc import instance, is_open 10 | 11 | __all__ = ['g_is_open'] 12 | 13 | 14 | def g_is_open(fn): 15 | @functools.wraps(fn) 16 | def _wrapper(*args, **kwargs): 17 | res = args[0] 18 | global is_open 19 | msg = res['Text'] 20 | to_user = res['ToUserName'] 21 | if msg == 'ro': 22 | is_open = True 23 | instance.send_msg('Miracle 微信机器人已开启', to_user) 24 | return 25 | if msg == 'rc': 26 | is_open = False 27 | instance.send_msg('Miracle 微信机器人已开启', to_user) 28 | return 29 | if is_open: 30 | return fn(*args, **kwargs) 31 | else: 32 | instance.send_msg(f'请发送指令:ro,打开机器人', to_user) 33 | return 34 | 35 | return _wrapper 36 | -------------------------------------------------------------------------------- /doc/wx/gzh_API.md: -------------------------------------------------------------------------------- 1 | ``` 2 | base_params = { 3 | "lang": "zh_CN", 4 | "f": "json", 5 | } 6 | ``` 7 | 8 | 9 | - 获取fakeid 10 | - method: GET 11 | - url: `https://mp.weixin.qq.com/cgi-bin/searchbiz` 12 | - data: 13 | ``` 14 | params = { 15 | 'action': 'search_biz', 16 | 'token': self._token, 17 | 'query': account, 18 | "ajax": "1", 19 | 'begin': 0, 20 | 'count': 5 21 | } 22 | ``` 23 | - 获取文章列表 24 | - method: GET 25 | - url: `https://mp.weixin.qq.com/cgi-bin/appmsg` 26 | - data: 27 | ``` 28 | params = { 29 | 'fakeid': fakeid, 30 | 'token': self._token, 31 | 'action': 'list_ex', 32 | 'begin': 0, 33 | 'count': 5, 34 | 'query': '', 35 | 'type': 9 36 | } 37 | ``` 38 | - 获取comment_id 39 | - method: POST 40 | - url: 文章url 41 | - data: 42 | ``` 43 | { 44 | "is_only_read": "1", 45 | "is_temp_url": "0", 46 | } 47 | ``` 48 | - 使用正则或者bs抓取 comment_id 49 | - 获取文章评论 50 | - method: GET 51 | - url: `https://mp.weixin.qq.com/mp/appmsg_comment` 52 | - data: 53 | ``` 54 | params = { 55 | 'action': 'getcomment', 56 | '__biz': __biz, 57 | 'idx': idx, 58 | 'comment_id': comment_id, 59 | 'limit': 100 60 | } 61 | ``` 62 | 63 | 64 | - -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # By Miracle 2 | .idea/ 3 | *.pkl 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /etc/base_cfg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/10/17 上午6:26 4 | 5 | __author__ = 'Miracle' 6 | 7 | import os 8 | 9 | PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 10 | LOG_DIR = os.path.join(PROJ_ROOT, 'log') 11 | TMP_DIR = os.path.join(PROJ_ROOT, 'tmp') 12 | STATIC_ROOT = os.path.join(PROJ_ROOT, 'static') 13 | WX_IMG_DIR = os.path.join(STATIC_ROOT, 'img/app') 14 | 15 | DEFAULT_LOGGING = { 16 | 'version': 1, 17 | 'disable_existing_loggers': False, 18 | 'formatters': { 19 | 'standard': { 20 | 'format': '%(asctime)s - %(filename)s[%(lineno)d] - %(levelname)s : %(message)s' 21 | }, 22 | }, 23 | 'handlers': { 24 | 'console': { 25 | 'level': 'INFO', 26 | 'formatter': 'standard', 27 | 'class': 'logging.StreamHandler', 28 | }, 29 | 'file_handler': { 30 | 'class': 'logging.handlers.TimedRotatingFileHandler', 31 | 'level': 'INFO', 32 | 'formatter': 'standard', 33 | 'filename': os.path.join(LOG_DIR, 'PIP.log'), 34 | # suffix: "%Y%m%d_%H%M%S.log" 35 | 'when': 'D', 36 | 'interval': 7, 37 | 'backupCount': 4, 38 | 'encoding': 'utf8' 39 | }, 40 | 'error_file_handler': { 41 | 'class': 'logging.handlers.RotatingFileHandler', 42 | 'level': 'ERROR', 43 | 'formatter': 'standard', 44 | 'filename': os.path.join(LOG_DIR, 'error.log'), 45 | 'maxBytes': 104857600, # 10MB 46 | 'backupCount': 20, 47 | 'encoding': 'utf8', 48 | } 49 | 50 | }, 51 | 'loggers': { 52 | 'myhandler': { 53 | 'handlers': ['console', 'file_handler', 'error_file_handler'], 54 | 'level': 'INFO', 55 | 'propagate': True 56 | }, 57 | }, 58 | 'root': { 59 | 'level': 'INFO', 60 | 'handlers': ['console', 'file_handler'], 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/wx/test_filehelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/18/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | import functools, threading, time 8 | import itchat 9 | from itchat.content import * 10 | 11 | import sys, os 12 | 13 | print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 14 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 15 | print(sys.path) 16 | 17 | instance = itchat.new_instance() 18 | 19 | 20 | class FileHelper: 21 | def __init__(self, instance): 22 | self.instance = instance 23 | self.th = threading.Thread(target=self.update_chatrooms, args=()) 24 | self.th.start() 25 | 26 | def update_chatrooms(self): 27 | while True: 28 | self.groups = [] 29 | all = self.instance.get_chatrooms() 30 | for group in all: 31 | self.groups.append(group.UserName) 32 | print(len(self.groups)) 33 | time.sleep(30) 34 | 35 | 36 | @instance.msg_register([FRIENDS], isFriendChat=True) 37 | def friends(res): 38 | msg = res['Text'] 39 | try: 40 | # add friends 41 | if res['MsgType'] == 51 and res['Status'] == 3 and res['StatusNotifyCode'] == 5: 42 | # TODO 43 | # add friends 44 | to_user = res.ToUserName 45 | instance.add_friend(userName=to_user, status=3) 46 | instance.send_msg(''' 47 | 由于人数已满100,回复:“技术群”,拉你入群。 48 | 知识星球内有「Python原创」、「大航海计划」、「问题解答」、「面试刷题」、「大厂内推」、「技术分享」等,在这个星球能够得到的,不只是关于Python,圈子、人脉、资源,学习氛围,眼界都是比技术更值得去借鉴的东西。 49 | 如果想要加入知识星球的话,可以回复“知识星球” 50 | ''', to_user=to_user) 51 | print('已添加好友') 52 | except AttributeError: 53 | pass 54 | 55 | 56 | @instance.msg_register([TEXT], isFriendChat=True) 57 | def auto_reply(res): 58 | msg = res['Text'] 59 | from_user = res['User'] 60 | if msg == '技术群': 61 | instance.add_member_into_chatroom(instance.search_chatrooms('测试群2')[0].UserName, 62 | memberList=[from_user]) 63 | elif msg == '知识星球': 64 | instance.send_image('./textpng.png', toUserName=from_user) 65 | else: 66 | pass 67 | 68 | 69 | fh = FileHelper(instance) 70 | 71 | instance.auto_login(hotReload=True) 72 | instance.run() 73 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/10/17 上午6:26 4 | 5 | __author__ = 'Miracle' 6 | 7 | import sys, os 8 | 9 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 10 | 11 | from itchat.content import * 12 | 13 | from core import fh, friend 14 | from utility import g_is_open 15 | from etc import instance, WX_IMG_DIR, TMP_DIR 16 | 17 | 18 | @instance.msg_register([FRIENDS], isFriendChat=True) 19 | @g_is_open 20 | def friends(res): 21 | try: 22 | # 添加好友 23 | msg = res['RecommendInfo']['Content'] 24 | username = res['RecommendInfo']['UserName'] 25 | nickname = res['RecommendInfo']['NickName'] 26 | add_ret = instance.add_friend(username, 3) 27 | if add_ret['BaseResponse']['ErrMsg'] == '请求成功': 28 | print(f'已添加好友: {nickname}') 29 | instance.send_msg(friend.meta['extra']['welcome'], username) 30 | # 修改备注 31 | # 若有商务、合作等关键字,备注: 商务- 32 | # 其他备注:Python专栏- 33 | if friend.is_biz(msg): 34 | instance.set_alias(username, f'商务-{nickname}') 35 | else: 36 | instance.set_alias(username, f'python专栏-{nickname}') 37 | instance.send_msg(f'添加好友: {nickname} 成功。', 'filehelper') 38 | else: 39 | print(f'添加好友失败: {nickname}') 40 | 41 | except Exception: 42 | pass 43 | 44 | 45 | @instance.msg_register([TEXT], isFriendChat=True) 46 | @g_is_open 47 | def file_helper(res): 48 | msg = res['Text'] 49 | from_user = res['FromUserName'] 50 | to_user = res['ToUserName'] 51 | if msg == '技术群': 52 | instance.send_msg('晚些我会统一拉你们入群~', to_user=from_user) 53 | return 54 | if msg == '知识星球': 55 | instance.send_image(os.path.join(WX_IMG_DIR, 'zsxq.jpeg'), toUserName=from_user['UserName']) 56 | return 57 | if to_user == fh.meta['extra']['NickName']: 58 | if msg == 'm': 59 | return fh.usage 60 | if not fh.current_cmd: 61 | cmd = '_'.join(msg.split()) 62 | fh.update_cmd(cmd) 63 | return 64 | if fh.current_cmd: 65 | eval(f'fh.{fh.current_cmd}')(msg) 66 | return 67 | 68 | 69 | if __name__ == '__main__': 70 | instance.auto_login( 71 | hotReload=True, 72 | statusStorageDir=os.path.join(TMP_DIR, 'wx_instance.pkl'), 73 | picDir=os.path.join(TMP_DIR, 'QR.png') 74 | ) 75 | instance.run() 76 | -------------------------------------------------------------------------------- /app/gzh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 2018/10/17 上午6:26 4 | 5 | __author__ = 'Miracle' 6 | 7 | import re 8 | 9 | import requests 10 | 11 | 12 | class Moment: 13 | def __init__(self, token, cookie, accounts): 14 | # token: 750784052 15 | self._token = token 16 | self._cookie = self.__extract_strck2dictck(cookie) 17 | self._urls = { 18 | 'get_fakeid': 'https://mp.weixin.qq.com/cgi-bin/searchbiz', 19 | 'get_articles_list': 'https://mp.weixin.qq.com/cgi-bin/appmsg', 20 | 'get_article_comments': 'https://mp.weixin.qq.com/mp/appmsg_comment' 21 | } 22 | self._params = { 23 | "lang": "zh_CN", 24 | "f": "json", 25 | } 26 | self._session = requests.Session() 27 | self._headers = { 28 | # 'Accept': 'application/json, text/javascript, */*; q=0.01', 29 | # 'Accept-Encoding': 'gzip, deflate, br', 30 | # 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7', 31 | # 'Connection': 'keep-alive', 32 | # 'Host': 'mp.weixin.qq.com', 33 | # 'Referer': 'https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=10&token=358402574&lang=zh_CN', 34 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 micromessage Safari/604.1', 35 | # 'X-Requested-With': 'XMLHttpRequest' 36 | } 37 | self._session.headers.update(self._headers) 38 | # {'xpchuiit': 'fakeid', ...} 39 | # fakeid: MzI2MjQ3MzEzMQ== 40 | self._accounts = {account: self.__get_fakeid(account) for account in accounts} 41 | 42 | def __get_fakeid(self, account): 43 | params = { 44 | 'action': 'search_biz', 45 | 'token': self._token, 46 | 'query': account, 47 | "ajax": "1", 48 | 'begin': 0, 49 | 'count': 5 50 | } 51 | params.update(self._params) 52 | return self._session.request('GET', self._urls['get_fakeid'], params=params, 53 | cookies=self._cookie).json()['list'][0]['fakeid'] 54 | 55 | def __extract_strck2dictck(self, ck): 56 | return {kv.strip().split('=', 1)[0]: kv.strip().split('=', 1)[1] for kv in ck.split(';')} 57 | 58 | def __get_articles_list(self, fakeid): 59 | params = { 60 | 'fakeid': fakeid, 61 | 'token': self._token, 62 | 'action': 'list_ex', 63 | 'begin': 0, 64 | 'count': 5, 65 | 'query': '', 66 | 'type': 9 67 | } 68 | params.update(self._params) 69 | return self._session.request('GET', self._urls['get_articles_list'], params=params).json()['app_msg_list'] 70 | 71 | def __get_comment_id(self, link): 72 | # appid, link = article['appmsgid'], article['link'] 73 | payload = { 74 | "is_only_read": "1", 75 | "is_temp_url": "0", 76 | } 77 | pattern = 'comment_id = "(?P\d+)"' 78 | m = re.compile(pattern) 79 | res = self._session.request('GET', link, json=payload).text 80 | return m.search(res).groupdict()['comment_id'] 81 | 82 | def __get_url_params(self, link): 83 | # link: https://mp.weixin.qq.com/s?__biz=MzI2MjQ3MzEzMQ==&mid=2247484165&idx=1&sn=dc5396655c25aa8c885b9480052b967a&chksm=ea4bd7c1dd3c5ed7788a01f0b310b5163c262db009d9a7ce9ae64545103f96ed52951fefb0fc#rd 84 | params = link.split('?', 1)[1].split('&') 85 | return {p.split('=', 1)[0]: p.split('=', 1)[1] for p in params} 86 | 87 | def get_latest_article(self, fakeid): 88 | return self.__get_articles_list(fakeid)[0] 89 | 90 | def get_comments(self, link): 91 | comment_id = self.__get_comment_id(link) 92 | url_params = self.__get_url_params(link) 93 | params = { 94 | 'action': 'getcomment', 95 | '__biz': url_params['__biz'], 96 | 'idx': url_params['idx'], 97 | 'comment_id': comment_id, 98 | 'limit': 100 99 | } 100 | params.update(self._params) 101 | return self._session.request('GET', self._urls['get_article_comments'], params=params) 102 | -------------------------------------------------------------------------------- /core/filehelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # @Time : 10/18/18 4 | 5 | __author__ = 'MiracleYoung' 6 | 7 | import functools, time, random, threading 8 | 9 | from core.base import BaseHandle 10 | from etc import RESTRICT_GROUP, LIMIT_GROUP, instance 11 | 12 | __all__ = ['fh'] 13 | 14 | 15 | class FileHelper(BaseHandle): 16 | _usage = ''' 17 | ''' 18 | 19 | def __init__(self): 20 | super().__init__() 21 | self._meta = { 22 | 'extra': { 23 | 'NickName': 'filehelper', 24 | 'UserName': 'filehelper', 25 | }, 26 | 'action': { 27 | 'mass': False, 28 | 'friend': False, 29 | }, 30 | 'obj': { 31 | 'ul': [], 32 | 'l': [], 33 | 'r': [], 34 | 'test': [] 35 | }, 36 | 'reply': { 37 | 'text': '请输入要群发的消息', 38 | 'article': '请输入要群发的文章链接', 39 | } 40 | } 41 | self._th_update = threading.Thread(target=self._update_meta, args=(), daemon=True) 42 | self.auto_update_groups() 43 | 44 | def auto_update_groups(self): 45 | self._th_update.start() 46 | 47 | def _update_meta(self): 48 | ''' 49 | 初始化限时推送的群组 50 | ''' 51 | 52 | def _filter_restrict_groups(group): 53 | for limit in RESTRICT_GROUP: 54 | if limit in group['NickName']: 55 | return True 56 | return False 57 | 58 | def _filter_limit_groups(group): 59 | for limit in LIMIT_GROUP: 60 | if limit in group['NickName']: 61 | return True 62 | return False 63 | 64 | def _filter_unlimit_group(groups, limit_groups): 65 | ret = [] 66 | for group in groups: 67 | for lg in limit_groups: 68 | if group['NickName'] == lg['NickName']: 69 | break 70 | else: 71 | ret.append(group) 72 | return ret 73 | 74 | while True: 75 | time.sleep(30) 76 | try: 77 | _all = instance.get_chatrooms(update=True) 78 | self._meta['obj']['l'] = list(filter(_filter_limit_groups, _all)) 79 | self._meta['obj']['r'] = list(filter(_filter_restrict_groups, _all)) 80 | self._meta['obj']['ul'] = _filter_unlimit_group( 81 | _all, self._meta['obj']['l'] + self._meta['obj']['r'] 82 | ) 83 | self._meta['obj']['test'] = [instance.search_chatrooms(group)[0] for group in ['测试群', '测试群2']] 84 | except Exception: 85 | self._meta['obj']['l'] = [] 86 | self._meta['obj']['ul'] = [] 87 | self._meta['obj']['r'] = [] 88 | 89 | print(len(self._meta['obj']['ul'])) 90 | 91 | def update_cmd(self, cmd): 92 | _action, _reply, _obj = cmd.split('_') 93 | self._meta['action'][_action] = True 94 | self.current_cmd = cmd 95 | instance.send_msg(self._meta['reply'][_reply], self._meta['extra']['UserName']) 96 | 97 | def _register_mass(func): 98 | @functools.wraps(func) 99 | def decorator(self, msg, *args, **kwargs): 100 | _action, _reply, _obj = func.__name__.split('_') 101 | if self._meta['action'][_action]: 102 | _to_user = self._meta['obj'][_obj] 103 | for _group in _to_user: 104 | instance.send_msg(msg, _group['UserName']) 105 | time.sleep(random.randrange(0, 20)) 106 | self._meta['action'][_action] = False 107 | self._current_cmd = None 108 | instance.send_msg('群发消息发送完毕', self._meta['extra']['UserName']) 109 | 110 | return decorator 111 | 112 | @_register_mass 113 | def mass_text_ul(self, msg=None): 114 | pass 115 | 116 | @_register_mass 117 | def mass_text_l(self, msg): 118 | pass 119 | 120 | @_register_mass 121 | def mass_text_test(self, msg): 122 | pass 123 | 124 | @_register_mass 125 | def mass_article_ul(self, msg): 126 | pass 127 | 128 | @_register_mass 129 | def mass_article_l(self, msg): 130 | pass 131 | 132 | 133 | fh = FileHelper() 134 | -------------------------------------------------------------------------------- /doc/wx/msg.js: -------------------------------------------------------------------------------- 1 | msg = { 2 | 'MsgId': '7731557286655305283', 3 | 'FromUserName': '@51df406780e0141d11022f04a83957d0', 4 | 'ToUserName': 'filehelper', 5 | 'MsgType': 1, 6 | 'Content': 'hello', 7 | 'Status': 3, 8 | 'ImgStatus': 1, 9 | 'CreateTime': 1523150490, 10 | 'VoiceLength': 0, 11 | 'PlayLength': 0, 12 | 'FileName': '', 13 | 'FileSize': '', 14 | 'MediaId': '', 15 | 'Url': '', 16 | 'AppMsgType': 0, 17 | 'StatusNotifyCode': 0, 18 | 'StatusNotifyUserName': '', 19 | 'RecommendInfo': { 20 | 'UserName': '', 21 | 'NickName': '', 22 | 'QQNum': 0, 23 | 'Province': '', 24 | 'City': '', 25 | 'Content': '', 26 | 'Signature': '', 27 | 'Alias': '', 28 | 'Scene': 0, 29 | 'VerifyFlag': 0, 30 | 'AttrStatus': 0, 31 | 'Sex': 0, 32 | 'Ticket': '', 33 | 'OpCode': 0 34 | }, 35 | 'ForwardFlag': 0, 36 | 'AppInfo': {'AppID': '', 'Type': 0}, 37 | 'HasProductId': 0, 38 | 'Ticket': '', 39 | 'ImgHeight': 0, 40 | 'ImgWidth': 0, 41 | 'SubMsgType': 0, 42 | 'NewMsgId': 7731557286655305283, 43 | 'OriContent': '', 44 | 'EncryFileName': '', 45 | 'User': "< User:{'UserName': 'filehelper', 'MemberList': } >", 46 | 'Type': 'Text', 47 | 'Text': 'hello' 48 | } 49 | 50 | group_text = { 51 | 'MsgId': '7107859226301119274', 52 | 'FromUserName': '@51df406780e0141d11022f04a83957d0', 53 | 'ToUserName': '@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03', 54 | 'MsgType': 1, 55 | 'Content': '我', 56 | 'Status': 3, 57 | 'ImgStatus': 1, 58 | 'CreateTime': 1523151032, 59 | 'VoiceLength': 0, 60 | 'PlayLength': 0, 61 | 'FileName': '', 62 | 'FileSize': '', 63 | 'MediaId': '', 64 | 'Url': '', 65 | 'AppMsgType': 0, 66 | 'StatusNotifyCode': 0, 67 | 'StatusNotifyUserName': '', 68 | 'RecommendInfo': { 69 | 'UserName': '', 70 | 'NickName': '', 71 | 'QQNum': 0, 72 | 'Province': '', 73 | 'City': '', 74 | 'Content': '', 75 | 'Signature': '', 76 | 'Alias': '', 77 | 'Scene': 0, 78 | 'VerifyFlag': 0, 79 | 'AttrStatus': 0, 80 | 'Sex': 0, 81 | 'Ticket': '', 82 | 'OpCode': 0 83 | }, 84 | 'ForwardFlag': 0, 85 | 'AppInfo': {'AppID': '', 'Type': 0}, 86 | 'HasProductId': 0, 87 | 'Ticket': '', 88 | 'ImgHeight': 0, 89 | 'ImgWidth': 0, 90 | 'SubMsgType': 0, 91 | 'NewMsgId': 7107859226301119274, 92 | 'OriContent': '', 93 | 'EncryFileName': '', 94 | 'ActualNickName': '上海小胖', 95 | 'IsAt': False, 96 | 'ActualUserName': '@51df406780e0141d11022f04a83957d0', 97 | 'User': ", 'Uin': 0, 'UserName': '@99afe1f436e703f86df8269e62c333787bc0f13979932fd074176246430f6f38', 'NickName': '杨霸霸', 'AttrStatus': 102437, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>, , 'Uin': 0, 'UserName': '@51df406780e0141d11022f04a83957d0', 'NickName': '上海小胖', 'AttrStatus': 33665663, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>]>, 'UserName': '@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03', 'NickName': '', 'Sex': 0, 'HeadImgUpdateFlag': 1, 'ContactType': 0, 'Alias': '', 'ChatRoomOwner': '@51df406780e0141d11022f04a83957d0', 'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=0&username=@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03&skey=@crypt_d88d2aa8_63ce18edaff4eecddca023c6ddb8b2f1', 'ContactFlag': 2, 'MemberCount': 2, 'HideInputBarFlag': 0, 'Signature': '', 'VerifyFlag': 0, 'RemarkName': '', 'Statues': 1, 'AttrStatus': 0, 'Province': '', 'City': '', 'SnsFlag': 0, 'KeyWord': '', 'OwnerUin': 0, 'IsAdmin': None, 'Self': , 'Uin': 0, 'UserName': '@51df406780e0141d11022f04a83957d0', 'NickName': '上海小胖', 'AttrStatus': 33665663, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>}>", 98 | 'Type': 'Text', 99 | 'Text': '我' 100 | } 101 | 102 | group_img = { 103 | 'MsgId': '2945261063824540297', 104 | 'FromUserName': '@51df406780e0141d11022f04a83957d0', 105 | 'ToUserName': '@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03', 106 | 'MsgType': 3, 107 | 'Content': '\n\n\t\n\n', 108 | 'Status': 3, 109 | 'ImgStatus': 2, 110 | 'CreateTime': 1523151724, 111 | 'VoiceLength': 0, 112 | 'PlayLength': 0, 113 | 'FileName': '180408-094204.png', 114 | 'FileSize': '', 115 | 'MediaId': '', 116 | 'Url': '', 117 | 'AppMsgType': 0, 118 | 'StatusNotifyCode': 0, 119 | 'StatusNotifyUserName': '', 120 | 'RecommendInfo': { 121 | 'UserName': '', 122 | 'NickName': '', 123 | 'QQNum': 0, 124 | 'Province': '', 125 | 'City': '', 126 | 'Content': '', 127 | 'Signature': '', 128 | 'Alias': '', 129 | 'Scene': 0, 130 | 'VerifyFlag': 0, 131 | 'AttrStatus': 0, 132 | 'Sex': 0, 133 | 'Ticket': '', 134 | 'OpCode': 0 135 | }, 136 | 'ForwardFlag': 0, 137 | 'AppInfo': {'AppID': '', 'Type': 0}, 138 | 'HasProductId': 0, 139 | 'Ticket': '', 140 | 'ImgHeight': 120, 141 | 'ImgWidth': 67, 142 | 'SubMsgType': 0, 143 | 'NewMsgId': 2945261063824540297, 144 | 'OriContent': '', 145 | 'EncryFileName': '', 146 | 'ActualNickName': '上海小胖', 147 | 'IsAt': False, 148 | 'ActualUserName': '@51df406780e0141d11022f04a83957d0', 149 | 'User': ", 'Uin': 0, 'UserName': '@99afe1f436e703f86df8269e62c333787bc0f13979932fd074176246430f6f38', 'NickName': '杨霸霸', 'AttrStatus': 102437, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>, , 'Uin': 0, 'UserName': '@51df406780e0141d11022f04a83957d0', 'NickName': '上海小胖', 'AttrStatus': 33665663, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>]>, 'UserName': '@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03', 'NickName': '测试群', 'Sex': 0, 'HeadImgUpdateFlag': 1, 'ContactType': 0, 'Alias': '', 'ChatRoomOwner': '@51df406780e0141d11022f04a83957d0', 'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=0&username=@@d6d81681fcd4925de20142a07c6eebbc5204d4faa514795622a2fdacd486ee03&skey=@crypt_d88d2aa8_63ce18edaff4eecddca023c6ddb8b2f1', 'ContactFlag': 2, 'MemberCount': 2, 'HideInputBarFlag': 0, 'Signature': '', 'VerifyFlag': 0, 'RemarkName': '', 'Statues': 1, 'AttrStatus': 0, 'Province': '', 'City': '', 'SnsFlag': 0, 'KeyWord': '', 'OwnerUin': 0, 'IsAdmin': None, 'Self': , 'Uin': 0, 'UserName': '@51df406780e0141d11022f04a83957d0', 'NickName': '上海小胖', 'AttrStatus': 33665663, 'PYInitial': '', 'PYQuanPin': '', 'RemarkPYInitial': '', 'RemarkPYQuanPin': '', 'MemberStatus': 0, 'DisplayName': '', 'KeyWord': ''}>}>", 150 | 'Type': 'Picture', 151 | 'Text': " < function get_download_fn. < locals >.download_fn at 0x1109f2620 > " 152 | } 153 | 154 | --------------------------------------------------------------------------------