├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── README.md ├── setup.py └── wechatrobot ├── Api.py ├── Bus.py ├── ChatRoomData.proto ├── ChatRoomData_pb2.py ├── Modles.py ├── Utils.py ├── WeChatRobot.py ├── __init__.py └── __version__.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .history/ 2 | example/ 3 | test.py 4 | __pychche__/ 5 | build/* 6 | dist/* 7 | *.egg-info/ 8 | 1.dat 9 | image.py 10 | testrobot.py 11 | wechatrobot/__pycache__/ 12 | db/ 13 | *.pyc 14 | docker* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | from wechatrobot import WeChatRobot 3 | 4 | 5 | bot = WeChatRobot() 6 | 7 | @bot.on("friend_msg") 8 | def on_friend_msg(msg): 9 | bot.SendText(wxid = msg['sender'], msg = msg['message']) 10 | 11 | @bot.on("group_msg") 12 | def on_group_msg(msg): 13 | print(f"on_group_msg: {msg}") 14 | 15 | @bot.on("self_msg") 16 | def on_self_msg(msg): 17 | print(f"on_self_msg: {msg}") 18 | 19 | bot.run() 20 | ``` 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import pathlib 3 | import re 4 | 5 | WORK_DIR = pathlib.Path(__file__).parent 6 | 7 | with open("README.md", "r", encoding="utf-8") as fh: 8 | long_description = fh.read() 9 | 10 | __version__ = "" 11 | exec(open('wechatrobot/__version__.py').read()) 12 | 13 | 14 | setup( 15 | name="python-comwechatrobot-http", 16 | version=__version__, 17 | description='http api for comwechatrobot', 18 | author='honus', 19 | author_email="honusmr@gmail.com", 20 | url="", 21 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 22 | python_requires='>=3.7', 23 | keywords=["wechatrobot"], 24 | install_requires=[ 25 | "requests", 26 | "pydantic", 27 | "protobuf==3.20.1", 28 | ], 29 | long_description=long_description, 30 | long_description_content_type="text/markdown", 31 | classifiers=[ 32 | 'Development Status :: 4 - Beta', 33 | 'Intended Audience :: Developers', 34 | 'Topic :: Software Development :: User Interfaces', 35 | 'License :: OSI Approved :: MIT License', 36 | 'Programming Language :: Python :: 3.7', 37 | "Operating System :: OS Independent" 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /wechatrobot/Api.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any, Union, Awaitable , Optional , Dict 2 | import requests 3 | import json 4 | from .Modles import * 5 | import base64 6 | from wechatrobot import ChatRoomData_pb2 as ChatRoom 7 | 8 | class Api: 9 | port : int = 18888 10 | db_handle : Dict[str, int] = 0 11 | 12 | def IsLoginIn(self , **params) -> Dict: 13 | return self.post(WECHAT_IS_LOGIN , IsLoginBody(**params)) 14 | 15 | def GetSelfInfo(self , **params) -> Dict: 16 | return self.post(WECHAT_GET_SELF_INFO , GetSelfInfoBody(**params)) 17 | 18 | def SendText(self , **params) -> Dict: 19 | return self.post(WECHAT_MSG_SEND_TEXT , SendTextBody(**params)) 20 | 21 | def SendAt(self , **params) -> Dict: 22 | return self.post(WECHAT_MSG_SEND_AT , SendAtBody(**params)) 23 | 24 | def SendCard(self , **params) -> Dict: 25 | return self.post(WECHAT_MSG_SEND_CARD , SendCardBody(**params)) 26 | 27 | def SendImage(self , **params) -> Dict: 28 | return self.post(WECHAT_MSG_SEND_IMAGE , SendImageBody(**params)) 29 | 30 | def SendFile(self , **params) -> Dict: 31 | return self.post(WECHAT_MSG_SEND_FILE , SendFileBody(**params)) 32 | 33 | def SendArticle(self , **params) -> Dict: 34 | return self.post(WECHAT_MSG_SEND_ARTICLE , SendArticleBody(**params)) 35 | 36 | def SendApp(self , **params) -> Dict: 37 | return self.post(WECHAT_MSG_SEND_APP , SendAppBody(**params)) 38 | 39 | def StartMsgHook(self, **params) -> Dict: 40 | return self.post(WECHAT_MSG_START_HOOK , StartMsgHookBody(**params)) 41 | 42 | def StopMsgHook(self , **params) -> Dict: 43 | return self.post(WECHAT_MSG_STOP_HOOK , StopMsgHookBody(**params)) 44 | 45 | def StartImageHook(self , **params) -> Dict: 46 | return self.post(WECHAT_MSG_START_IMAGE_HOOK , StartImageHookBody(**params)) 47 | 48 | def StopImageHook(self , **params) -> Dict: 49 | return self.post(WECHAT_MSG_STOP_IMAGE_HOOK , StopImageHookBody(**params)) 50 | 51 | def StartVoiceHook(self , **params) -> Dict: 52 | return self.post(WECHAT_MSG_START_VOICE_HOOK , StartVoiceHookBody(**params)) 53 | 54 | def StopVoiceHook(self , **params) -> Dict: 55 | return self.post(WECHAT_MSG_STOP_VOICE_HOOK , StopVoiceHookBody(**params)) 56 | 57 | def GetContactList(self , **params) -> Dict: 58 | return self.post(WECHAT_CONTACT_GET_LIST , GetContactListBody(**params)) 59 | 60 | def CheckContactStatus(self , **params) -> Dict: 61 | return self.post(WECHAT_CONTACT_CHECK_STATUS , CheckContactStatusBody(**params)) 62 | 63 | def DelContact(self , **params) -> Dict: 64 | return self.post(WECHAT_CONTACT_DEL , DelContactBody(**params)) 65 | 66 | def SearchContactByCache(self , **params) -> Dict: 67 | return self.post(WECHAT_CONTACT_SEARCH_BY_CACHE , SearchContactByCacheBody(**params)) 68 | 69 | def SearchContactByNet(self , **params) -> Dict: 70 | return self.post(WECHAT_CONTACT_SEARCH_BY_NET , SearchContactByNetBody(**params)) 71 | 72 | def AddContactByWxid(self , **params) -> Dict: 73 | return self.post(WECHAT_CONTACT_ADD_BY_WXID , AddContactByWxidBody(**params)) 74 | 75 | def AddContactByV3(self , **params) -> Dict: 76 | return self.post(WECHAT_CONTACT_ADD_BY_V3 , AddContactByV3Body(**params)) 77 | 78 | def AddContactByPublicId(self , **params) -> Dict: 79 | return self.post(WECHAT_CONTACT_ADD_BY_PUBLIC_ID , AddContactByPublicIdBody(**params)) 80 | 81 | def VerifyApply(self , **params) -> Dict: 82 | return self.post(WECHAT_CONTACT_VERIFY_APPLY , VerifyApplyBody(**params)) 83 | 84 | def EditRemark(self , **params) -> Dict: 85 | return self.post(WECHAT_CONTACT_EDIT_REMARK , EditRemarkBody(**params)) 86 | 87 | def GetChatroomMemberList(self , **params) -> Dict: 88 | return self.post(WECHAT_CHATROOM_GET_MEMBER_LIST , GetChatroomMemberListBody(**params)) 89 | 90 | def GetChatroomMemberNickname(self , **params) -> Dict: 91 | return self.post(WECHAT_CHATROOM_GET_MEMBER_NICKNAME , GetChatroomMemberNicknameBody(**params)) 92 | 93 | def DelChatroomMember(self , **params) -> Dict: 94 | return self.post(WECHAT_CHATROOM_DEL_MEMBER , DelChatroomMemberBody(**params)) 95 | 96 | def AddChatroomMember(self , **params) -> Dict: 97 | return self.post(WECHAT_CHATROOM_ADD_MEMBER , AddChatroomMemberBody(**params)) 98 | 99 | def SetChatroomAnnouncement(self , **params) -> Dict: 100 | return self.post(WECHAT_CHATROOM_SET_ANNOUNCEMENT , SetChatroomAnnouncementBody(**params)) 101 | 102 | def SetChatroomName(self , **params) -> Dict: 103 | return self.post(WECHAT_CHATROOM_SET_CHATROOM_NAME , SetChatroomNameBody(**params)) 104 | 105 | def SetChatroomSelfNickname(self , **params) -> Dict: 106 | return self.post(WECHAT_CHATROOM_SET_SELF_NICKNAME , SetChatroomSelfNicknameBody(**params)) 107 | 108 | def GetDatabaseHandles(self , **params) -> Dict: 109 | return self.post(WECHAT_DATABASE_GET_HANDLES , GetDatabaseHandlesBody(**params)) 110 | 111 | def BackupDatabase(self , **params) -> Dict: 112 | return self.post(WECHAT_DATABASE_BACKUP , BackupDatabaseBody(**params)) 113 | 114 | def QueryDatabase(self , **params) -> Dict: 115 | return self.post(WECHAT_DATABASE_QUERY , QueryDatabaseBody(**params)) 116 | 117 | def SetVersion(self , **params) -> Dict: 118 | return self.post(WECHAT_SET_VERSION , SetVersionBody(**params)) 119 | 120 | def StartLogHook(self , **params) -> Dict: 121 | return self.post(WECHAT_LOG_START_HOOK , StartLogHookBody(**params)) 122 | 123 | def StopLogHook(self , **params) -> Dict: 124 | return self.post(WECHAT_LOG_STOP_HOOK , StopLogHookBody(**params)) 125 | 126 | def OpenBrowserWithUrl(self , **params) -> Dict: 127 | return self.post(WECHAT_BROWSER_OPEN_WITH_URL , OpenBrowserWithUrlBody(**params)) 128 | 129 | def GetPublicMsg(self , **params) -> Dict: 130 | return self.post(WECHAT_GET_PUBLIC_MSG , GetPublicMsgBody(**params)) 131 | 132 | def ForwardMessage(self , **params) -> Dict: 133 | return self.post(WECHAT_MSG_FORWARD_MESSAGE , ForwardMessageBody(**params)) 134 | 135 | def GetQrcodeImage(self , **params): 136 | r = requests.post( f"http://127.0.0.1:{self.port}/api/?type={WECHAT_GET_QRCODE_IMAGE}", data = GetQrcodeImageBody(**params).json()) 137 | return r.content 138 | 139 | def GetA8Key(self , **params) -> Dict: 140 | return self.post(WECHAT_GET_A8KEY , GetA8KeyBody(**params)) 141 | 142 | def SendXml(self , **params) -> Dict: 143 | return self.post(WECHAT_MSG_SEND_XML , SendXmlBody(**params)) 144 | 145 | def LogOut(self , **params) -> Dict: 146 | return self.post(WECHAT_LOGOUT , LogOutBody(**params)) 147 | 148 | def GetTransfer(self , **params) -> Dict: 149 | return self.post(WECHAT_GET_TRANSFER , GetTransferBody(**params)) 150 | 151 | def SendEmotion(self , **params) -> Dict: 152 | return self.post(WECHAT_MSG_SEND_EMOTION , SendEmotionBody(**params)) 153 | 154 | def GetCdn(self , **params) -> Dict: 155 | return self.post(WECHAT_GET_CDN , GetCdnBody(**params)) 156 | 157 | #[自定义 158 | def GetDBHandle(self, db_name="MicroMsg.db") -> int: 159 | if not self.db_handle: 160 | self.db_handle = {i["db_name"]: i["handle"] for i in self.GetDatabaseHandles()["data"]} 161 | 162 | return self.db_handle[db_name] 163 | 164 | def GetContactListBySql(self) -> Dict: 165 | sql = "select UserName,Alias,Remark,NickName,Type from Contact" # where type!=4 and type!=0; 166 | ContactList = self.QueryDatabase(db_handle=self.GetDBHandle(), sql=sql)["data"] 167 | contact_data = {} # {wxid : {alias, remark, nickname , type}} 168 | for index in range(1, len(ContactList)): 169 | wxid = ContactList[index][0] 170 | contact_data[wxid] = {} 171 | contact_data[wxid]['alias'] = ContactList[index][1] 172 | contact_data[wxid]['remark'] = ContactList[index][2] 173 | contact_data[wxid]['nickname'] = ContactList[index][3] 174 | contact_data[wxid]['type'] = ContactList[index][4] 175 | 176 | sql = "select UserName,'' as Alias,Remark,NickName,Type from OpenIMContact" # where type!=4 and type!=0; 177 | OpenIMContactList = self.QueryDatabase(db_handle=self.GetDBHandle("OpenIMContact.db"), sql=sql)["data"] 178 | for index in range(1, len(OpenIMContactList)): 179 | wxid = OpenIMContactList[index][0] 180 | contact_data[wxid] = {} 181 | contact_data[wxid]['alias'] = ContactList[index][1] 182 | contact_data[wxid]['remark'] = OpenIMContactList[index][2] 183 | contact_data[wxid]['nickname'] = OpenIMContactList[index][3] 184 | contact_data[wxid]['type'] = OpenIMContactList[index][4] 185 | return contact_data 186 | 187 | def GetAllGroupMembersBySql(self) -> Dict: 188 | group_data = {} #{"group_id" : { "wxID" : "displayName"}} 189 | sql = "select ChatRoomName,RoomData from ChatRoom" 190 | GroupMemberList = self.QueryDatabase(db_handle=self.GetDBHandle(), sql = sql)['data'] 191 | chatroom = ChatRoom.ChatRoomData() 192 | for index in range(1 , len(GroupMemberList)): 193 | group_member = {} 194 | chatroom.ParseFromString(bytes(base64.b64decode(GroupMemberList[index][1]))) 195 | for k in chatroom.members: 196 | if k.displayName != "": 197 | group_member[k.wxID] = k.displayName 198 | group_data[GroupMemberList[index][0]] = group_member 199 | return group_data 200 | 201 | def GetPictureBySql(self, wxid) -> Dict: 202 | if not wxid.endswith("@openim"): 203 | sql = f"select usrName,smallHeadImgUrl,bigHeadImgUrl from ContactHeadImgUrl where usrName='{wxid}';" 204 | result = self.QueryDatabase(db_handle=self.GetDBHandle(),sql=sql) 205 | else: 206 | sql = f"select UserName,SmallHeadImgUrl,BigHeadImgUrl from OpenIMContact where UserName='{wxid}';" 207 | result = self.QueryDatabase(db_handle=self.GetDBHandle("OpenIMContact.db"),sql=sql) 208 | try: 209 | if result["data"][1][2] != "": 210 | return result["data"][1][2] 211 | if result["data"][1][1] != "": 212 | return result["data"][1][1] 213 | return None 214 | except: 215 | return None 216 | 217 | def GetContactBySql(self, wxid): 218 | if not wxid.endswith("@openim"): 219 | sql = f"select UserName,Alias,Remark,NickName,Type from Contact where UserName='{wxid}';" 220 | result = self.QueryDatabase(db_handle=self.GetDBHandle(),sql=sql) 221 | else: 222 | sql = f"select UserName,'' as Alias,Remark,NickName,Type from OpenIMContact where UserName='{wxid}';" 223 | result = self.QueryDatabase(db_handle=self.GetDBHandle("OpenIMContact.db"),sql=sql) 224 | if len(result["data"]) > 0: 225 | return result["data"][1] 226 | else: 227 | return None 228 | #自定义] 229 | 230 | def post(self , type : int, params : Body) -> Dict: 231 | return json.loads(requests.post( f"http://127.0.0.1:{self.port}/api/?type={type}", data = params.json()).content.decode("utf-8"),strict=False) 232 | 233 | def exec_command(self , item: str) -> Callable: 234 | return eval(f"self.{item}") 235 | 236 | 237 | -------------------------------------------------------------------------------- /wechatrobot/Bus.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Any 2 | from collections import defaultdict 3 | 4 | class EventBus: 5 | def __init__(self): 6 | self._subscribers = defaultdict(set) 7 | 8 | def subscribe(self, event: str, func: Callable) -> None: 9 | self._subscribers[event].add(func) 10 | 11 | def emit(self, event: str, *args, **kwargs) -> List[Any]: 12 | results = [] 13 | for f in self._subscribers[event]: 14 | results.append(f(*args, **kwargs)) 15 | return results -------------------------------------------------------------------------------- /wechatrobot/ChatRoomData.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package RS_WXBOT_COM.protobuf; 3 | 4 | message ChatRoomData { 5 | 6 | message ChatRoomMember { 7 | string wxID = 1; 8 | string displayName = 2; 9 | int32 state = 3; 10 | } 11 | 12 | repeated ChatRoomMember members = 1; 13 | 14 | int32 field_2 = 2; 15 | int32 field_3 = 3; 16 | int32 field_4 = 4; 17 | int32 room_capacity = 5; 18 | int32 field_6 = 6; 19 | int64 field_7 = 7; 20 | int64 field_8 = 8; 21 | } -------------------------------------------------------------------------------- /wechatrobot/ChatRoomData_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: wechatrobot/ChatRoomData.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='wechatrobot/ChatRoomData.proto', 18 | package='RS_WXBOT_COM.protobuf', 19 | syntax='proto3', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n\x1ewechatrobot/ChatRoomData.proto\x12\x15RS_WXBOT_COM.protobuf\"\x94\x02\n\x0c\x43hatRoomData\x12\x43\n\x07members\x18\x01 \x03(\x0b\x32\x32.RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember\x12\x0f\n\x07\x66ield_2\x18\x02 \x01(\x05\x12\x0f\n\x07\x66ield_3\x18\x03 \x01(\x05\x12\x0f\n\x07\x66ield_4\x18\x04 \x01(\x05\x12\x15\n\rroom_capacity\x18\x05 \x01(\x05\x12\x0f\n\x07\x66ield_6\x18\x06 \x01(\x05\x12\x0f\n\x07\x66ield_7\x18\x07 \x01(\x03\x12\x0f\n\x07\x66ield_8\x18\x08 \x01(\x03\x1a\x42\n\x0e\x43hatRoomMember\x12\x0c\n\x04wxID\x18\x01 \x01(\t\x12\x13\n\x0b\x64isplayName\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\x05\x62\x06proto3' 23 | ) 24 | 25 | 26 | 27 | 28 | _CHATROOMDATA_CHATROOMMEMBER = _descriptor.Descriptor( 29 | name='ChatRoomMember', 30 | full_name='RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='wxID', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember.wxID', index=0, 38 | number=1, type=9, cpp_type=9, label=1, 39 | has_default_value=False, default_value=b"".decode('utf-8'), 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='displayName', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember.displayName', index=1, 45 | number=2, type=9, cpp_type=9, label=1, 46 | has_default_value=False, default_value=b"".decode('utf-8'), 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | _descriptor.FieldDescriptor( 51 | name='state', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember.state', index=2, 52 | number=3, type=5, cpp_type=1, label=1, 53 | has_default_value=False, default_value=0, 54 | message_type=None, enum_type=None, containing_type=None, 55 | is_extension=False, extension_scope=None, 56 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 57 | ], 58 | extensions=[ 59 | ], 60 | nested_types=[], 61 | enum_types=[ 62 | ], 63 | serialized_options=None, 64 | is_extendable=False, 65 | syntax='proto3', 66 | extension_ranges=[], 67 | oneofs=[ 68 | ], 69 | serialized_start=268, 70 | serialized_end=334, 71 | ) 72 | 73 | _CHATROOMDATA = _descriptor.Descriptor( 74 | name='ChatRoomData', 75 | full_name='RS_WXBOT_COM.protobuf.ChatRoomData', 76 | filename=None, 77 | file=DESCRIPTOR, 78 | containing_type=None, 79 | create_key=_descriptor._internal_create_key, 80 | fields=[ 81 | _descriptor.FieldDescriptor( 82 | name='members', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.members', index=0, 83 | number=1, type=11, cpp_type=10, label=3, 84 | has_default_value=False, default_value=[], 85 | message_type=None, enum_type=None, containing_type=None, 86 | is_extension=False, extension_scope=None, 87 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 88 | _descriptor.FieldDescriptor( 89 | name='field_2', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_2', index=1, 90 | number=2, type=5, cpp_type=1, label=1, 91 | has_default_value=False, default_value=0, 92 | message_type=None, enum_type=None, containing_type=None, 93 | is_extension=False, extension_scope=None, 94 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 95 | _descriptor.FieldDescriptor( 96 | name='field_3', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_3', index=2, 97 | number=3, type=5, cpp_type=1, label=1, 98 | has_default_value=False, default_value=0, 99 | message_type=None, enum_type=None, containing_type=None, 100 | is_extension=False, extension_scope=None, 101 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 102 | _descriptor.FieldDescriptor( 103 | name='field_4', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_4', index=3, 104 | number=4, type=5, cpp_type=1, label=1, 105 | has_default_value=False, default_value=0, 106 | message_type=None, enum_type=None, containing_type=None, 107 | is_extension=False, extension_scope=None, 108 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 109 | _descriptor.FieldDescriptor( 110 | name='room_capacity', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.room_capacity', index=4, 111 | number=5, type=5, cpp_type=1, label=1, 112 | has_default_value=False, default_value=0, 113 | message_type=None, enum_type=None, containing_type=None, 114 | is_extension=False, extension_scope=None, 115 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 116 | _descriptor.FieldDescriptor( 117 | name='field_6', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_6', index=5, 118 | number=6, type=5, cpp_type=1, label=1, 119 | has_default_value=False, default_value=0, 120 | message_type=None, enum_type=None, containing_type=None, 121 | is_extension=False, extension_scope=None, 122 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 123 | _descriptor.FieldDescriptor( 124 | name='field_7', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_7', index=6, 125 | number=7, type=3, cpp_type=2, label=1, 126 | has_default_value=False, default_value=0, 127 | message_type=None, enum_type=None, containing_type=None, 128 | is_extension=False, extension_scope=None, 129 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 130 | _descriptor.FieldDescriptor( 131 | name='field_8', full_name='RS_WXBOT_COM.protobuf.ChatRoomData.field_8', index=7, 132 | number=8, type=3, cpp_type=2, label=1, 133 | has_default_value=False, default_value=0, 134 | message_type=None, enum_type=None, containing_type=None, 135 | is_extension=False, extension_scope=None, 136 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 137 | ], 138 | extensions=[ 139 | ], 140 | nested_types=[_CHATROOMDATA_CHATROOMMEMBER, ], 141 | enum_types=[ 142 | ], 143 | serialized_options=None, 144 | is_extendable=False, 145 | syntax='proto3', 146 | extension_ranges=[], 147 | oneofs=[ 148 | ], 149 | serialized_start=58, 150 | serialized_end=334, 151 | ) 152 | 153 | _CHATROOMDATA_CHATROOMMEMBER.containing_type = _CHATROOMDATA 154 | _CHATROOMDATA.fields_by_name['members'].message_type = _CHATROOMDATA_CHATROOMMEMBER 155 | DESCRIPTOR.message_types_by_name['ChatRoomData'] = _CHATROOMDATA 156 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 157 | 158 | ChatRoomData = _reflection.GeneratedProtocolMessageType('ChatRoomData', (_message.Message,), { 159 | 160 | 'ChatRoomMember' : _reflection.GeneratedProtocolMessageType('ChatRoomMember', (_message.Message,), { 161 | 'DESCRIPTOR' : _CHATROOMDATA_CHATROOMMEMBER, 162 | '__module__' : 'wechatrobot.ChatRoomData_pb2' 163 | # @@protoc_insertion_point(class_scope:RS_WXBOT_COM.protobuf.ChatRoomData.ChatRoomMember) 164 | }) 165 | , 166 | 'DESCRIPTOR' : _CHATROOMDATA, 167 | '__module__' : 'wechatrobot.ChatRoomData_pb2' 168 | # @@protoc_insertion_point(class_scope:RS_WXBOT_COM.protobuf.ChatRoomData) 169 | }) 170 | _sym_db.RegisterMessage(ChatRoomData) 171 | _sym_db.RegisterMessage(ChatRoomData.ChatRoomMember) 172 | 173 | 174 | # @@protoc_insertion_point(module_scope) 175 | -------------------------------------------------------------------------------- /wechatrobot/Modles.py: -------------------------------------------------------------------------------- 1 | from pydantic.v1 import BaseModel 2 | 3 | # login check 4 | WECHAT_IS_LOGIN = 0 # 登录检查 5 | 6 | # self info 7 | WECHAT_GET_SELF_INFO = 1 # 获取个人信息 8 | 9 | # send message 10 | WECHAT_MSG_SEND_TEXT = 2 # 发送文本 11 | WECHAT_MSG_SEND_AT = 3 # 发送群艾特 12 | WECHAT_MSG_SEND_CARD = 4 # 分享好友名片 13 | WECHAT_MSG_SEND_IMAGE = 5 # 发送图片 14 | WECHAT_MSG_SEND_FILE = 6 # 发送文件 15 | WECHAT_MSG_SEND_ARTICLE = 7 # 发送xml文章 16 | WECHAT_MSG_SEND_APP = 8 # 发送小程序 17 | 18 | # receive message 19 | WECHAT_MSG_START_HOOK = 9 # 开启接收消息HOOK,只支持socket监听 20 | WECHAT_MSG_STOP_HOOK = 10 # 关闭接收消息HOOK 21 | WECHAT_MSG_START_IMAGE_HOOK = 11 # 开启图片消息HOOK 22 | WECHAT_MSG_STOP_IMAGE_HOOK = 12 # 关闭图片消息HOOK 23 | WECHAT_MSG_START_VOICE_HOOK = 13 # 开启语音消息HOOK 24 | WECHAT_MSG_STOP_VOICE_HOOK = 14 # 关闭语音消息HOOK 25 | 26 | # contact 27 | WECHAT_CONTACT_GET_LIST = 15 # 获取联系人列表 28 | WECHAT_CONTACT_CHECK_STATUS = 16 # 检查是否被好友删除 29 | WECHAT_CONTACT_DEL = 17 # 删除好友 30 | WECHAT_CONTACT_SEARCH_BY_CACHE = 18 # 从内存中获取好友信息 31 | WECHAT_CONTACT_SEARCH_BY_NET = 19 # 网络搜索用户信息 32 | WECHAT_CONTACT_ADD_BY_WXID = 20 # wxid加好友 33 | WECHAT_CONTACT_ADD_BY_V3 = 21 # v3数据加好友 34 | WECHAT_CONTACT_ADD_BY_PUBLIC_ID = 22 # 关注公众号 35 | WECHAT_CONTACT_VERIFY_APPLY = 23 # 通过好友请求 36 | WECHAT_CONTACT_EDIT_REMARK = 24 # 修改备注 37 | 38 | # chatroom 39 | WECHAT_CHATROOM_GET_MEMBER_LIST = 25 # 获取群成员列表 40 | WECHAT_CHATROOM_GET_MEMBER_NICKNAME = 26 # 获取指定群成员昵称 41 | WECHAT_CHATROOM_DEL_MEMBER = 27 # 删除群成员 42 | WECHAT_CHATROOM_ADD_MEMBER = 28 # 添加群成员 43 | WECHAT_CHATROOM_SET_ANNOUNCEMENT = 29 # 设置群公告 44 | WECHAT_CHATROOM_SET_CHATROOM_NAME = 30 # 设置群聊名称 45 | WECHAT_CHATROOM_SET_SELF_NICKNAME = 31 # 设置群内个人昵称 46 | 47 | # database 48 | WECHAT_DATABASE_GET_HANDLES = 32 # 获取数据库句柄 49 | WECHAT_DATABASE_BACKUP = 33 # 备份数据库 50 | WECHAT_DATABASE_QUERY = 34 # 数据库查询 51 | 52 | # version 53 | WECHAT_SET_VERSION = 35 # 修改微信版本号 54 | 55 | # log 56 | WECHAT_LOG_START_HOOK = 36 # 开启日志信息HOOK 57 | WECHAT_LOG_STOP_HOOK = 37 # 关闭日志信息HOOK 58 | 59 | # browser 60 | WECHAT_BROWSER_OPEN_WITH_URL = 38 # 打开微信内置浏览器 61 | WECHAT_GET_PUBLIC_MSG = 39 # 获取公众号历史消息 62 | 63 | WECHAT_MSG_FORWARD_MESSAGE = 40 # 转发消息 64 | WECHAT_GET_QRCODE_IMAGE = 41 # 获取二维码 65 | WECHAT_GET_A8KEY = 42 # 获取A8Key 66 | WECHAT_MSG_SEND_XML = 43 # 发送xml消息 67 | WECHAT_LOGOUT = 44 # 退出登录 68 | WECHAT_GET_TRANSFER = 45 # 收款 69 | WECHAT_MSG_SEND_EMOTION = 46 # 发送表情 70 | WECHAT_GET_CDN = 47 # 下载文件、视频、图片 71 | 72 | # Body 73 | 74 | class Body(BaseModel): 75 | ... 76 | 77 | #login check 78 | class IsLoginBody(Body): 79 | ... 80 | 81 | #self info 82 | class GetSelfInfoBody(Body): 83 | ... 84 | 85 | #send message 86 | class SendTextBody(Body): 87 | wxid : str 88 | msg : str 89 | 90 | class SendAtBody(Body): 91 | chatroom_id : str 92 | wxids : str 93 | msg : str 94 | auto_nickname: int = 1 95 | 96 | class SendCardBody(Body): 97 | receiver : str 98 | share_wxid : str 99 | nickname : str 100 | 101 | class SendImageBody(Body): 102 | receiver : str 103 | img_path : str 104 | 105 | class SendFileBody(Body): 106 | receiver : str 107 | file_path : str 108 | 109 | class SendArticleBody(Body): 110 | wxid : str 111 | title : str 112 | abstract : str 113 | url : str 114 | img_path : str 115 | 116 | class SendAppBody(Body): 117 | wxid : str 118 | appid: str 119 | 120 | #receive message 121 | class StartMsgHookBody(Body): 122 | port : int 123 | 124 | class StopMsgHookBody(Body): 125 | ... 126 | 127 | class StartImageHookBody(Body): 128 | save_path : str 129 | 130 | class StopImageHookBody(Body): 131 | ... 132 | 133 | class StartVoiceHookBody(Body): 134 | save_path : str 135 | 136 | class StopVoiceHookBody(Body): 137 | ... 138 | 139 | #contact 140 | class GetContactListBody(Body): 141 | ... 142 | 143 | class CheckContactStatusBody(Body): 144 | wxid : str 145 | 146 | class DelContactBody(Body): 147 | wxid : str 148 | 149 | class SearchContactByCacheBody(Body): 150 | wxid : str 151 | 152 | class SearchContactByNetBody(Body): 153 | keyword : str 154 | 155 | class AddContactByWxidBody(Body): 156 | wxid : str 157 | msg : str 158 | 159 | class AddContactByV3Body(Body): 160 | v3 : str 161 | msg : str 162 | add_type : int = 0x6 163 | 164 | class AddContactByPublicIdBody(Body): 165 | public_id : str 166 | 167 | class VerifyApplyBody(Body): 168 | v3 : str 169 | v4 : str 170 | 171 | class EditRemarkBody(Body): 172 | wxid : str 173 | remark: str 174 | 175 | #chatroom 176 | class GetChatroomMemberListBody(Body): 177 | chatroom_id : str 178 | 179 | class GetChatroomMemberNicknameBody(Body): 180 | chatroom_id : str 181 | wxid : str 182 | 183 | class DelChatroomMemberBody(Body): 184 | chatroom_id : str 185 | wxids : str # split by "," 186 | 187 | class AddChatroomMemberBody(Body): 188 | chatroom_id : str 189 | wxids : str # split by "," 190 | 191 | class SetChatroomAnnouncementBody(Body): 192 | chatroom_id : str 193 | announcement: str 194 | 195 | class SetChatroomNameBody(Body): 196 | chatroom_id : str 197 | chatroom_name : str 198 | 199 | class SetChatroomSelfNicknameBody(Body): 200 | chatroom_id : str 201 | nickname : str 202 | 203 | #database 204 | class GetDatabaseHandlesBody(Body): 205 | ... 206 | 207 | class BackupDatabaseBody(Body): 208 | db_handle : str 209 | save_path : str 210 | 211 | class QueryDatabaseBody(Body): 212 | db_handle : str 213 | sql : str 214 | 215 | #version 216 | class SetVersionBody(Body): 217 | version : str # "3.7.0.30" 218 | 219 | #log 220 | class StartLogHookBody(Body): 221 | ... 222 | 223 | class StopLogHookBody(Body): 224 | ... 225 | 226 | #browser 227 | class OpenBrowserWithUrlBody(Body): 228 | url : str 229 | 230 | class GetPublicMsgBody(Body): 231 | public_id : str 232 | offset : str 233 | 234 | #forward message 235 | class ForwardMessageBody(Body): 236 | wxid : str 237 | msgid : str 238 | 239 | #qrcode 240 | class GetQrcodeImageBody(Body): 241 | ... 242 | 243 | #a8key 244 | class GetA8KeyBody(Body): 245 | url : str 246 | 247 | #send xml 248 | class SendXmlBody(Body): 249 | wxid : str 250 | xml : str 251 | img_path : str 252 | 253 | #logout 254 | class LogOutBody(Body): 255 | ... 256 | 257 | #get transfer 258 | class GetTransferBody(Body): 259 | wxid : str 260 | transcationid : str 261 | transferid : str 262 | 263 | #send emotion 264 | class SendEmotionBody(Body): 265 | wxid : str 266 | img_path : str 267 | 268 | #get cdn 269 | class GetCdnBody(Body): 270 | msgid : int 271 | -------------------------------------------------------------------------------- /wechatrobot/Utils.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wechatrobot/WeChatRobot.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | import threading 3 | import logging 4 | import socketserver 5 | import requests 6 | import json 7 | 8 | from .Api import Api 9 | from .Bus import EventBus 10 | 11 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 12 | 13 | Bus = EventBus() 14 | 15 | class WeChatRobot: 16 | BASE_PATH = "C:\\Users\\user\\My Documents\\WeChat Files" 17 | 18 | def __init__(self , ip : str = "0.0.0.0" , port : int = 23456): 19 | self.ip = ip 20 | self.port = port 21 | self.api = Api() 22 | 23 | self.url = "http://{}:{}/".format(ip , port) 24 | 25 | def on(self , *event_type : str) -> Callable: 26 | def deco(func: Callable) -> Callable: 27 | for _type in event_type: 28 | Bus.subscribe( _type , func) 29 | return deco 30 | return deco 31 | 32 | def run(self , main_thread : bool = True): 33 | #StartHook 34 | self.StartMsgHook(port = self.port) 35 | self.StartImageHook(save_path = self.BASE_PATH) 36 | self.StartVoiceHook(save_path = self.BASE_PATH) 37 | 38 | class ReceiveMsgSocketServer(socketserver.BaseRequestHandler): 39 | def __init__(self, *args, **kwargs): 40 | super().__init__(*args, **kwargs) 41 | 42 | def receive_callback(self , msg): 43 | type_dict = { 44 | 0 : 'eventnotify', # 成员变更 45 | 1 : 'text', 46 | 3 : 'image', 47 | 9 : 'scancashmoney', # 面对面付款 48 | 34 : 'voice', 49 | 35 : 'qqmail', 50 | 37 : 'friendrequest', 51 | 42 : 'card', 52 | 43 : 'video', 53 | 47 : 'animatedsticker', 54 | 48 : 'location', 55 | 49 : 'share', 56 | 50 : 'voip', 57 | 51 : 'phone', 58 | 106 : 'sysnotify', # system notification 修改群名称 59 | 1009 : 'eventnotify', # 减少成员 60 | 1010 : 'eventnotify', # 添加成员 61 | 2000 : 'transfer', 62 | 2001 : 'redpacket', 63 | 2002 : 'miniprogram', 64 | 2003 : 'groupinvite', 65 | 2004 : 'file', 66 | 2005 : 'revokemsg', 67 | 2006 : 'groupannouncement', 68 | 10000 : 'sysmsg', # 拍一拍 语音消息 撤回消息 邀请入群 移出群 69 | 10002 : 'other' # multivoip , taptap , ClientCheckConsistency, 邀请加入群聊并分享历史消息 70 | } 71 | 72 | msg['type'] = type_dict.get(msg['type'] , 'unhandled'+str(msg['type'])) 73 | 74 | if msg["type"] == "friendrequest": 75 | Bus.emit("frdver_msg" , msg) 76 | elif msg["type"] == "card": 77 | Bus.emit("card_msg" , msg) 78 | elif '' in msg["message"]: 79 | Bus.emit("revoke_msg", msg) 80 | elif "微信转账" in msg["message"] and "1" in msg["message"]: 81 | Bus.emit("transfer_msg", msg) 82 | elif (1 == msg["isSendMsg"]): 83 | if 1 == msg["isSendByPhone"]: 84 | Bus.emit("self_msg", msg) 85 | elif "chatroom" in msg["sender"]: 86 | Bus.emit("group_msg", msg) 87 | else: 88 | Bus.emit("friend_msg", msg) 89 | 90 | def handle(self): 91 | conn = self.request 92 | while True: 93 | try: 94 | ptr_data = b"" 95 | while True: 96 | data = conn.recv(1024) 97 | ptr_data += data 98 | if len(data) == 0 or data[-1] == 0xA: 99 | conn.sendall("200 OK".encode()) 100 | break 101 | msg = json.loads(ptr_data.decode('utf-8')) 102 | self.receive_callback(msg) 103 | except OSError: 104 | break 105 | except json.JSONDecodeError: 106 | pass 107 | except UnicodeDecodeError as e: 108 | pass 109 | conn.close() 110 | 111 | ip_port = ( self.ip , self.port ) 112 | try: 113 | s = socketserver.ThreadingTCPServer(ip_port , ReceiveMsgSocketServer) 114 | if main_thread: 115 | s.serve_forever() 116 | else: 117 | socket_server = threading.Thread(target=s.serve_forever) 118 | socket_server.setDaemon(True) 119 | socket_server.start() 120 | return socket_server.ident 121 | except KeyboardInterrupt: 122 | pass 123 | except Exception as e: 124 | logging.error(e) 125 | return None 126 | 127 | def get_base_path(self): 128 | return self.BASE_PATH 129 | 130 | def __getattr__(self , item : str): 131 | return self.api.exec_command(item) 132 | -------------------------------------------------------------------------------- /wechatrobot/__init__.py: -------------------------------------------------------------------------------- 1 | from .Api import * 2 | from .Bus import * 3 | from .Modles import * 4 | from .Utils import * 5 | from .WeChatRobot import WeChatRobot -------------------------------------------------------------------------------- /wechatrobot/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.2" --------------------------------------------------------------------------------