wxRobot.py 29.3 KB
Newer Older
1 2 3 4 5 6 7
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 24 16:19:48 2022

@author: ljc545w
"""

L
ljc545w 已提交
8
# Before use,execute `CWeChatRobot.exe /regserver` in cmd by admin user
9
import ast
L
ljc545w 已提交
10
import os
L
ljc545w 已提交
11 12
import ctypes
import ctypes.wintypes
L
ljc545w 已提交
13
import socketserver
14
import threading
L
ljc545w 已提交
15 16
# need `pip install comtypes`
import comtypes.client
L
ljc545w 已提交
17 18
from comtypes.client import GetEvents
from comtypes.client import PumpEvents
L
ljc545w 已提交
19

L
ljc545w 已提交
20 21

class _WeChatRobotClient:
L
ljc545w 已提交
22
    _instance = None
L
ljc545w 已提交
23

L
ljc545w 已提交
24
    @classmethod
L
ljc545w 已提交
25
    def instance(cls) -> '_WeChatRobotClient':
L
ljc545w 已提交
26 27 28
        if not cls._instance:
            cls._instance = cls()
        return cls._instance
L
ljc545w 已提交
29

L
ljc545w 已提交
30 31 32
    def __init__(self):
        self.robot = comtypes.client.CreateObject("WeChatRobot.CWeChatRobot")
        self.event = comtypes.client.CreateObject("WeChatRobot.RobotEvent")
L
ljc545w 已提交
33 34
        self.com_pid = self.robot.CStopRobotService(0)

L
ljc545w 已提交
35 36 37 38 39
    @classmethod
    def __del__(cls):
        import psutil
        if cls._instance is not None:
            try:
L
ljc545w 已提交
40 41
                com_process = psutil.Process(cls._instance.com_pid)
                com_process.kill()
L
ljc545w 已提交
42 43 44
            except psutil.NoSuchProcess:
                pass
        cls._instance = None
L
ljc545w 已提交
45

L
ljc545w 已提交
46 47

class WeChatEventSink:
L
ljc545w 已提交
48 49 50 51
    """
    接收消息的默认回调,可以自定义,并将实例化对象作为StartReceiveMsgByEvent参数
    自定义的类需要包含以下所有成员
    """
L
ljc545w 已提交
52 53

    def OnGetMessageEvent(self, msg):
L
ljc545w 已提交
54
        print(msg)
L
ljc545w 已提交
55

L
ljc545w 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

class ReceiveMsgBaseServer(socketserver.BaseRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    class ReceiveMsgStruct(ctypes.Structure):
        _fields_ = [("pid", ctypes.wintypes.DWORD),
                    ("type", ctypes.wintypes.DWORD),
                    ("isSendMsg", ctypes.wintypes.DWORD),
                    ("sender", ctypes.c_wchar * 80),
                    ("wxid", ctypes.c_wchar * 80),
                    ("message", ctypes.c_wchar * 0x1000B),
                    ("filepath", ctypes.c_wchar * 260),
                    ("time", ctypes.c_wchar * 30)
                    ]

L
ljc545w 已提交
72 73 74 75 76
    def handle(self):
        conn = self.request
        comtypes.CoInitialize()
        while True:
            try:
L
ljc545w 已提交
77
                ptr_data = conn.recv(1024)
L
ljc545w 已提交
78
                try:
L
ljc545w 已提交
79
                    if ptr_data.decode() == 'bye':
L
ljc545w 已提交
80
                        break
L
ljc545w 已提交
81
                except UnicodeDecodeError:
L
ljc545w 已提交
82
                    pass
L
ljc545w 已提交
83
                while len(ptr_data) < ctypes.sizeof(self.ReceiveMsgStruct):
L
ljc545w 已提交
84 85 86
                    data = conn.recv(1024)
                    if len(data) == 0:
                        break
L
ljc545w 已提交
87 88 89 90
                    ptr_data += data
                if ptr_data:
                    ptr_receive_msg = ctypes.cast(ptr_data, ctypes.POINTER(self.ReceiveMsgStruct))
                    ReceiveMsgBaseServer.msg_callback(ptr_receive_msg.contents)
L
ljc545w 已提交
91 92 93 94
                response = "200 OK"
                conn.sendall(response.encode())
            except OSError:
                break
L
ljc545w 已提交
95 96
            except Exception as e:
                print(e)
L
ljc545w 已提交
97 98 99
                conn.sendall("200 OK".encode())
        conn.close()
        comtypes.CoUninitialize()
L
ljc545w 已提交
100 101 102

    @staticmethod
    def msg_callback(data):
L
ljc545w 已提交
103
        # 主线程中已经注入,此处禁止调用StartService和StopService
L
ljc545w 已提交
104 105
        msg = {'pid': data.pid, 'time': data.time, 'type': data.type, 'isSendMsg': data.isSendMsg, 'wxid': data.wxid,
               'sendto' if data.isSendMsg else 'from': data.sender, 'message': data.message}
L
ljc545w 已提交
106 107
        robot = comtypes.client.CreateObject("WeChatRobot.CWeChatRobot")
        event = comtypes.client.CreateObject("WeChatRobot.RobotEvent")
L
ljc545w 已提交
108
        wx = WeChatRobot(data.pid, robot, event)
L
ljc545w 已提交
109
        userinfo = wx.GetWxUserInfo(data.wxid)
L
ljc545w 已提交
110
        msg['alias'] = userinfo['wxNumber']
L
ljc545w 已提交
111 112
        if data.isSendMsg == 0:
            if '@chatroom' in data.sender:
L
ljc545w 已提交
113 114
                chatroom_info = wx.GetWxUserInfo(data.sender)
                msg['chatroom_name'] = chatroom_info['wxNickName']
L
ljc545w 已提交
115 116 117 118
                msg['nickname'] = wx.GetChatRoomMemberNickname(data.sender, data.wxid)
            else:
                msg['nickname'] = userinfo['wxNickName']
        # TODO: 在这里写额外的消息处理逻辑
L
ljc545w 已提交
119

L
ljc545w 已提交
120
        print(msg)
L
ljc545w 已提交
121 122
        robot.Release()
        event.Release()
123

L
ljc545w 已提交
124 125 126

class ChatSession:
    def __init__(self, pid, robot, wxid):
L
ljc545w 已提交
127
        self.pid = pid
128
        self.robot = robot
L
ljc545w 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
        self.chat_with = wxid

    def SendText(self, msg):
        return self.robot.CSendText(self.pid, self.chat_with, msg)

    def SendImage(self, img_path):
        return self.robot.CSendImage(self.pid, self.chat_with, img_path)

    def SendFile(self, filepath):
        return self.robot.CSendFile(self.pid, self.chat_with, filepath)

    def SendMp4(self, mp4path):
        return self.robot.CSendImage(self.pid, self.chat_with, mp4path)

    def SendArticle(self, title, abstract, url, img_path=None):
        return self.robot.CSendArticle(self.pid, self.chat_with, title, abstract, url, img_path)

    def SendCard(self, shared_wxid, nickname):
        return self.robot.CSendCard(self.pid, self.chat_with, shared_wxid, nickname)

    def SendAtText(self, wxid: list or str or tuple, msg, auto_nickname=True):
        if '@chatroom' not in self.chat_with:
L
ljc545w 已提交
151
            return 1
L
ljc545w 已提交
152 153 154 155 156
        return self.robot.CSendAtText(self.pid, self.chat_with, wxid, msg, auto_nickname)

    def SendAppMsg(self, appid):
        return self.robot.CSendAppMsg(self.pid, self.chat_with, appid)

L
ljc545w 已提交
157

L
ljc545w 已提交
158
class WeChatRobot:
159

L
ljc545w 已提交
160
    def __init__(self, pid: int = 0, robot=None, event=None):
L
ljc545w 已提交
161 162 163
        self.pid = pid
        self.robot = robot or _WeChatRobotClient.instance().robot
        self.event = event or _WeChatRobotClient.instance().event
164
        self.AddressBook = []
L
ljc545w 已提交
165

L
ljc545w 已提交
166 167 168 169 170 171 172 173 174 175
    def StartService(self) -> int:
        """
        注入DLL到微信以启动服务

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
176
        status = self.robot.CStartRobotService(self.pid)
177
        return status
L
ljc545w 已提交
178

L
ljc545w 已提交
179 180 181 182 183 184 185 186 187 188
    def IsWxLogin(self) -> int:
        """
        获取微信登录状态

        Returns
        -------
        bool
            微信登录状态.

        """
L
ljc545w 已提交
189
        return self.robot.CIsWxLogin(self.pid)
190

L
ljc545w 已提交
191
    def SendText(self, receiver: str, msg: str) -> int:
L
ljc545w 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        """
        发送文本消息

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
        msg : str
            消息内容.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
208 209 210
        return self.robot.CSendText(self.pid, receiver, msg)

    def SendImage(self, receiver: str, img_path: str) -> int:
L
ljc545w 已提交
211 212 213 214 215 216 217
        """
        发送图片消息

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
L
ljc545w 已提交
218
        img_path : str
L
ljc545w 已提交
219 220 221 222 223 224 225 226
            图片绝对路径.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
227 228 229
        return self.robot.CSendImage(self.pid, receiver, img_path)

    def SendFile(self, receiver: str, filepath: str) -> int:
L
ljc545w 已提交
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        """
        发送文件

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
        filepath : str
            文件绝对路径.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
246 247 248
        return self.robot.CSendFile(self.pid, receiver, filepath)

    def SendArticle(self, receiver: str, title: str, abstract: str, url: str, img_path: str or None = None) -> int:
L
ljc545w 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261
        """
        发送XML文章

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
        title : str
            消息卡片标题.
        abstract : str
            消息卡片摘要.
        url : str
            文章链接.
L
ljc545w 已提交
262
        img_path : str or None, optional
L
ljc545w 已提交
263 264 265 266 267 268 269 270
            消息卡片显示的图片绝对路径,不需要可以不指定. The default is None.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
271 272 273
        return self.robot.CSendArticle(self.pid, receiver, title, abstract, url, img_path)

    def SendCard(self, receiver: str, shared_wxid: str, nickname: str) -> int:
L
ljc545w 已提交
274 275 276 277 278 279 280
        """
        发送名片

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
L
ljc545w 已提交
281
        shared_wxid : str
L
ljc545w 已提交
282 283 284 285 286 287 288 289 290 291
            被分享人wxid.
        nickname : str
            名片显示的昵称.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
292 293 294
        return self.robot.CSendCard(self.pid, receiver, shared_wxid, nickname)

    def SendAtText(self, chatroom_id: str, at_users: list or str or tuple, msg: str, auto_nickname: bool = True) -> int:
L
ljc545w 已提交
295 296 297 298 299
        """
        发送群艾特消息,艾特所有人可以将AtUsers设置为`notify@all`
        无目标群管理权限请勿使用艾特所有人
        Parameters
        ----------
L
ljc545w 已提交
300
        chatroom_id : str
L
ljc545w 已提交
301
            群聊ID.
L
ljc545w 已提交
302
        at_users : list or str or tuple
L
ljc545w 已提交
303 304 305
            被艾特的人列表.
        msg : str
            消息内容.
L
ljc545w 已提交
306
        auto_nickname : bool, optional
L
ljc545w 已提交
307 308 309 310 311 312 313 314
            是否自动填充被艾特人昵称. 默认自动填充.

        Returns
        -------
        int
            0成功,非0失败.

        """
L
ljc545w 已提交
315
        if '@chatroom' not in chatroom_id:
L
ljc545w 已提交
316
            return 1
L
ljc545w 已提交
317
        return self.robot.CSendAtText(self.pid, chatroom_id, at_users, msg, auto_nickname)
L
ljc545w 已提交
318

L
ljc545w 已提交
319 320 321 322 323 324 325 326 327 328
    def GetSelfInfo(self) -> dict:
        """
        获取个人信息

        Returns
        -------
        dict
            调用成功返回个人信息,否则返回空字典.

        """
L
ljc545w 已提交
329
        self_info = self.robot.CGetSelfInfo(self.pid).replace('\n', '\\n')
330
        try:
L
ljc545w 已提交
331
            self_info = ast.literal_eval(self_info)
332 333
        except SyntaxError:
            return {}
L
ljc545w 已提交
334 335
        return self_info

L
ljc545w 已提交
336 337
    def StopService(self) -> int:
        """
L
ljc545w 已提交
338
        停止服务,会将DLL从微信进程中卸载
L
ljc545w 已提交
339 340 341 342 343 344 345

        Returns
        -------
        int
            COM进程pid.

        """
L
ljc545w 已提交
346 347 348
        com_pid = self.robot.CStopRobotService(self.pid)
        return com_pid

L
ljc545w 已提交
349 350 351 352 353 354 355 356 357 358
    def GetAddressBook(self) -> list:
        """
        获取联系人列表

        Returns
        -------
        list
            调用成功返回通讯录列表,调用失败返回空列表.

        """
L
ljc545w 已提交
359
        try:
L
ljc545w 已提交
360 361
            friend_tuple = self.robot.CGetFriendList(self.pid)
            self.AddressBook = [dict(i) for i in list(friend_tuple)]
L
ljc545w 已提交
362 363
        except IndexError:
            self.AddressBook = []
364
        return self.AddressBook
L
ljc545w 已提交
365

L
ljc545w 已提交
366 367 368 369 370 371 372 373 374 375
    def GetFriendList(self) -> list:
        """
        从通讯录列表中筛选出好友列表

        Returns
        -------
        list
            好友列表.

        """
376 377
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
378
        friend_list = []
379 380
        for item in self.AddressBook:
            if 'wxid_' == item['wxid'][0:5]:
L
ljc545w 已提交
381 382 383
                friend_list.append(item)
        return friend_list

L
ljc545w 已提交
384 385 386 387 388 389 390 391 392 393
    def GetChatRoomList(self) -> list:
        """
        从通讯录列表中筛选出群聊列表

        Returns
        -------
        list
            群聊列表.

        """
394 395
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
396
        chatroom_list = []
397 398
        for item in self.AddressBook:
            if '@chatroom' in item['wxid']:
L
ljc545w 已提交
399 400 401
                chatroom_list.append(item)
        return chatroom_list

L
ljc545w 已提交
402 403 404 405 406 407 408 409 410 411
    def GetOfficialAccountList(self) -> list:
        """
        从通讯录列表中筛选出公众号列表

        Returns
        -------
        list
            公众号列表.

        """
412 413
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
414
        official_account_list = []
415 416
        for item in self.AddressBook:
            if 'wxid_' != item['wxid'][0:5] and '@chatroom' not in item['wxid']:
L
ljc545w 已提交
417 418 419 420
                official_account_list.append(item)
        return official_account_list

    def GetFriendByWxRemark(self, remark: str) -> dict or None:
L
ljc545w 已提交
421 422 423 424 425 426 427 428 429 430 431 432 433 434
        """
        通过备注搜索联系人

        Parameters
        ----------
        remark : str
            好友备注.

        Returns
        -------
        dict or None
            搜索到返回联系人信息,否则返回None.

        """
435 436 437 438 439 440
        if not self.AddressBook:
            self.GetAddressBook()
        for item in self.AddressBook:
            if item['wxRemark'] == remark:
                return item
        return None
L
ljc545w 已提交
441 442

    def GetFriendByWxNumber(self, wx_number: str) -> dict or None:
L
ljc545w 已提交
443 444 445 446 447
        """
        通过微信号搜索联系人

        Parameters
        ----------
L
ljc545w 已提交
448
        wx_number : str
L
ljc545w 已提交
449 450 451 452 453 454 455 456
            联系人微信号.

        Returns
        -------
        dict or None
            搜索到返回联系人信息,否则返回None.

        """
457 458 459
        if not self.AddressBook:
            self.GetAddressBook()
        for item in self.AddressBook:
L
ljc545w 已提交
460
            if item['wxNumber'] == wx_number:
461 462
                return item
        return None
L
ljc545w 已提交
463 464

    def GetFriendByWxNickName(self, nickname: str) -> dict or None:
L
ljc545w 已提交
465 466 467 468 469
        """
        通过昵称搜索联系人

        Parameters
        ----------
L
ljc545w 已提交
470
        nickname : str
L
ljc545w 已提交
471 472 473 474 475 476 477 478
            联系人昵称.

        Returns
        -------
        dict or None
            搜索到返回联系人信息,否则返回None.

        """
479 480 481
        if not self.AddressBook:
            self.GetAddressBook()
        for item in self.AddressBook:
L
ljc545w 已提交
482
            if item['wxNickName'] == nickname:
483 484
                return item
        return None
L
ljc545w 已提交
485 486

    def GetChatSession(self, wxid: str) -> 'ChatSession':
L
ljc545w 已提交
487 488 489 490 491 492 493 494 495 496 497 498 499 500
        """
        创建一个会话,没太大用处

        Parameters
        ----------
        wxid : str
            联系人wxid.

        Returns
        -------
        'ChatSession'
            返回ChatSession类.

        """
L
ljc545w 已提交
501 502 503
        return ChatSession(self.pid, self.robot, wxid)

    def GetWxUserInfo(self, wxid: str) -> dict:
L
ljc545w 已提交
504 505 506 507 508 509 510 511 512 513 514 515 516 517
        """
        通过wxid查询联系人信息

        Parameters
        ----------
        wxid : str
            联系人wxid.

        Returns
        -------
        dict
            联系人信息.

        """
L
ljc545w 已提交
518
        userinfo = self.robot.CGetWxUserInfo(self.pid, wxid).replace('\n', '\\n')
519
        return ast.literal_eval(userinfo)
L
ljc545w 已提交
520 521

    def GetChatRoomMembers(self, chatroom_id: str) -> dict or None:
L
ljc545w 已提交
522 523 524 525 526
        """
        获取群成员信息

        Parameters
        ----------
L
ljc545w 已提交
527
        chatroom_id : str
L
ljc545w 已提交
528 529 530 531
            群聊id.

        Returns
        -------
L
ljc545w 已提交
532 533
        dict or None
            获取成功返回群成员信息,失败返回None.
L
ljc545w 已提交
534 535

        """
L
ljc545w 已提交
536
        info = dict(self.robot.CGetChatRoomMembers(self.pid, chatroom_id))
L
ljc545w 已提交
537 538 539
        if not info:
            return None
        members = info['members'].split('^G')
L
ljc545w 已提交
540
        data = self.GetWxUserInfo(chatroom_id)
L
ljc545w 已提交
541 542
        data['members'] = []
        for member in members:
L
ljc545w 已提交
543 544
            member_info = self.GetWxUserInfo(member)
            data['members'].append(member_info)
L
ljc545w 已提交
545
        return data
L
ljc545w 已提交
546 547

    def CheckFriendStatus(self, wxid: str) -> int:
L
ljc545w 已提交
548 549 550 551 552 553 554 555 556 557 558 559
        """
        获取好友状态码

        Parameters
        ----------
        wxid : str
            好友wxid.

        Returns
        -------
        int
            0x0: 'Unknown',
560 561
            0xB0:'被删除',
            0xB1:'是好友',
L
ljc545w 已提交
562
            0xB2:'已拉黑',
563
            0xB5:'被拉黑',
L
ljc545w 已提交
564 565

        """
L
ljc545w 已提交
566 567
        return self.robot.CCheckFriendStatus(self.pid, wxid)

L
ljc545w 已提交
568
    # 接收消息的函数
L
ljc545w 已提交
569
    def StartReceiveMessage(self, port: int = 10808) -> int:
L
ljc545w 已提交
570
        """
L
ljc545w 已提交
571
        启动接收消息Hook
L
ljc545w 已提交
572 573 574

        Parameters
        ----------
L
ljc545w 已提交
575
        port : int
L
ljc545w 已提交
576
            socket的监听端口号.如果要使用连接点回调,则将端口号设置为0.
L
ljc545w 已提交
577 578 579 580 581 582 583

        Returns
        -------
        int
            启动成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
584
        status = self.robot.CStartReceiveMessage(self.pid, port)
585
        return status
L
ljc545w 已提交
586

L
ljc545w 已提交
587
    def StopReceiveMessage(self) -> int:
L
ljc545w 已提交
588
        """
L
ljc545w 已提交
589
        停止接收消息Hook
L
ljc545w 已提交
590 591 592

        Returns
        -------
L
ljc545w 已提交
593 594
        int
            成功返回0,失败返回非0值.
L
ljc545w 已提交
595 596

        """
L
ljc545w 已提交
597 598
        status = self.robot.CStopReceiveMessage(self.pid)
        return status
L
ljc545w 已提交
599

L
ljc545w 已提交
600 601 602 603 604 605 606 607 608 609
    def GetDbHandles(self) -> dict:
        """
        获取数据库句柄和表信息

        Returns
        -------
        dict
            数据库句柄和表信息.

        """
L
ljc545w 已提交
610 611
        tables_tuple = self.robot.CGetDbHandles(self.pid)
        tables = [dict(i) for i in tables_tuple]
L
ljc545w 已提交
612 613 614 615
        dbs = {}
        for table in tables:
            dbname = table['dbname']
            if dbname not in dbs.keys():
L
ljc545w 已提交
616
                dbs[dbname] = {'Handle': table['Handle'], 'tables': []}
L
ljc545w 已提交
617
            dbs[dbname]['tables'].append(
L
ljc545w 已提交
618 619 620
                {'name': table['name'], 'tbl_name': table['tbl_name'],
                 'root_page': table['root_page'], 'sql': table['sql']}
            )
L
ljc545w 已提交
621
        return dbs
L
ljc545w 已提交
622 623

    def ExecuteSQL(self, handle: int, sql: str) -> list:
L
ljc545w 已提交
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
        """
        执行SQL

        Parameters
        ----------
        handle : int
            数据库句柄.
        sql : str
            SQL.

        Returns
        -------
        list
            查询结果.

        """
L
ljc545w 已提交
640
        result = self.robot.CExecuteSQL(self.pid, handle, sql)
L
ljc545w 已提交
641 642
        if len(result) == 0:
            return []
L
ljc545w 已提交
643 644 645 646
        query_list = []
        keys = list(result[0])
        for item in result[1:]:
            query_dict = {}
L
ljc545w 已提交
647
            for key, value in zip(keys, item):
L
ljc545w 已提交
648 649 650
                query_dict[key] = value if not isinstance(value, tuple) else bytes(value)
            query_list.append(query_dict)
        return query_list
L
ljc545w 已提交
651 652

    def BackupSQLiteDB(self, handle: int, filepath: str) -> int:
L
ljc545w 已提交
653 654 655 656 657 658 659
        """
        备份数据库

        Parameters
        ----------
        handle : int
            数据库句柄.
L
ljc545w 已提交
660
        filepath : int
L
ljc545w 已提交
661 662 663 664 665 666 667 668
            备份文件保存位置.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
669 670 671 672 673 674 675
        filepath = filepath.replace('/', '\\')
        save_path = filepath.replace(filepath.split('\\')[-1], '')
        if not os.path.exists(save_path):
            os.makedirs(save_path)
        return self.robot.CBackupSQLiteDB(self.pid, handle, filepath)

    def VerifyFriendApply(self, v3: str, v4: str) -> int:
L
ljc545w 已提交
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
        """
        通过好友请求

        Parameters
        ----------
        v3 : str
            v3数据(encryptUserName).
        v4 : str
            v4数据(ticket).

        Returns
        -------
        int
            成功返回0,失败返回非0值..

        """
L
ljc545w 已提交
692 693 694
        return self.robot.CVerifyFriendApply(self.pid, v3, v4)

    def AddFriendByWxid(self, wxid: str, message: str or None) -> int:
L
ljc545w 已提交
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
        """
        wxid加好友

        Parameters
        ----------
        wxid : str
            要添加的wxid.
        message : str or None
            验证信息.

        Returns
        -------
        int
            请求发送成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
711 712 713
        return self.robot.CAddFriendByWxid(self.pid, wxid, message)

    def AddFriendByV3(self, v3: str, message: str or None, add_type: int = 0x6) -> int:
L
ljc545w 已提交
714
        """
L
ljc545w 已提交
715 716 717 718
        v3数据加好友

        Parameters
        ----------
L
ljc545w 已提交
719
        v3 : str
L
ljc545w 已提交
720 721 722
            v3数据(encryptUserName).
        message : str or None
            验证信息.
L
ljc545w 已提交
723
        add_type : int
L
ljc545w 已提交
724 725 726 727 728 729 730
            添加方式(来源).手机号: 0xF;微信号: 0x3;QQ号: 0x1;朋友验证消息: 0x6.

        Returns
        -------
        int
            请求发送成功返回0,失败返回非0值.

L
ljc545w 已提交
731
        """
L
ljc545w 已提交
732 733
        return self.robot.CAddFriendByV3(self.pid, v3, message, add_type)

L
ljc545w 已提交
734 735 736 737 738 739 740 741 742 743
    def GetWeChatVer(self) -> str:
        """
        获取微信版本号

        Returns
        -------
        str
            微信版本号.

        """
L
ljc545w 已提交
744
        return self.robot.CGetWeChatVer()
L
ljc545w 已提交
745 746

    def GetUserInfoByNet(self, keyword: str) -> dict or None:
L
ljc545w 已提交
747 748 749 750 751 752 753 754 755 756 757 758 759 760
        """
        网络查询用户信息

        Parameters
        ----------
        keyword : str
            查询关键字,可以是微信号、手机号、QQ号.

        Returns
        -------
        dict or None
            查询成功返回用户信息,查询失败返回None.

        """
L
ljc545w 已提交
761
        userinfo = self.robot.CSearchContactByNet(self.pid, keyword)
L
ljc545w 已提交
762 763
        if userinfo:
            return dict(userinfo)
L
ljc545w 已提交
764
        return None
L
ljc545w 已提交
765 766

    def AddBrandContact(self, public_id: str) -> int:
L
ljc545w 已提交
767 768 769 770 771
        """
        关注公众号

        Parameters
        ----------
L
ljc545w 已提交
772
        public_id : str
L
ljc545w 已提交
773 774 775 776 777 778 779 780
            公众号id.

        Returns
        -------
        int
            请求成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
781 782 783
        return self.robot.CAddBrandContact(self.pid, public_id)

    def ChangeWeChatVer(self, version: str) -> int:
L
ljc545w 已提交
784
        """
L
ljc545w 已提交
785 786 787 788 789 790 791 792 793 794 795 796
        自定义微信版本号,一定程度上防止自动更新

        Parameters
        ----------
        version : str
            版本号,类似`3.7.0.26`

        Returns
        -------
        int
            成功返回0,失败返回非0值.

L
ljc545w 已提交
797
        """
L
ljc545w 已提交
798 799 800
        return self.robot.CChangeWeChatVer(self.pid, version)

    def HookImageMsg(self, save_path: str) -> int:
L
ljc545w 已提交
801 802 803 804 805
        """
        开始Hook未加密图片

        Parameters
        ----------
L
ljc545w 已提交
806
        save_path : str
L
ljc545w 已提交
807 808 809 810 811 812 813 814
            图片保存路径(绝对路径).

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
815 816
        return self.robot.CHookImageMsg(self.pid, save_path)

L
ljc545w 已提交
817 818 819 820 821 822 823 824 825 826
    def UnHookImageMsg(self) -> int:
        """
        取消Hook未加密图片

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
827
        return self.robot.CUnHookImageMsg(self.pid)
L
ljc545w 已提交
828 829

    def HookVoiceMsg(self, save_path: str) -> int:
L
ljc545w 已提交
830 831 832 833 834
        """
        开始Hook语音消息

        Parameters
        ----------
L
ljc545w 已提交
835
        save_path : str
L
ljc545w 已提交
836 837 838 839 840 841 842 843
            语音保存路径(绝对路径).

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
844 845
        return self.robot.CHookVoiceMsg(self.pid, save_path)

L
ljc545w 已提交
846 847 848 849 850 851 852 853 854 855
    def UnHookVoiceMsg(self) -> int:
        """
        取消Hook语音消息

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
856
        return self.robot.CUnHookVoiceMsg(self.pid)
L
ljc545w 已提交
857

L
ljc545w 已提交
858
    def DeleteUser(self, wxid: str) -> int:
L
ljc545w 已提交
859 860 861 862 863 864 865 866 867 868 869 870 871 872
        """
        删除好友

        Parameters
        ----------
        wxid : str
            被删除好友wxid.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
873
        return self.robot.CDeleteUser(self.pid, wxid)
L
ljc545w 已提交
874

L
ljc545w 已提交
875
    def SendAppMsg(self, wxid: str, appid: str) -> int:
L
ljc545w 已提交
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
        """
        发送小程序

        Parameters
        ----------
        wxid : str
            消息接收者wxid.
        appid : str
            小程序id (在xml中是username,不是appid).

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
892 893 894
        return self.robot.CSendAppMsg(self.pid, wxid, appid)

    def EditRemark(self, wxid: str, remark: str or None) -> int:
L
ljc545w 已提交
895 896 897 898 899 900
        """
        修改好友或群聊备注

        Parameters
        ----------
        wxid : str
L
ljc545w 已提交
901
            wxid或chatroom_id.
L
ljc545w 已提交
902 903 904 905 906 907 908 909 910
        remark : str or None
            要修改的备注.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
911 912 913
        return self.robot.CEditRemark(self.pid, wxid, remark)

    def SetChatRoomName(self, chatroom_id: str, name: str) -> int:
L
ljc545w 已提交
914 915 916 917 918
        """
        修改群名称.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
919
        chatroom_id : str
L
ljc545w 已提交
920 921 922 923 924 925 926 927 928 929
            群聊id.
        name : str
            要修改为的群名称.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
930 931 932
        return self.robot.CSetChatRoomName(self.pid, chatroom_id, name)

    def SetChatRoomAnnouncement(self, chatroom_id: str, announcement: str or None) -> int:
L
ljc545w 已提交
933 934 935 936 937
        """
        设置群公告.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
938
        chatroom_id : str
L
ljc545w 已提交
939 940 941 942 943 944 945 946 947 948
            群聊id.
        announcement : str or None
            公告内容.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
949 950 951
        return self.robot.CSetChatRoomAnnouncement(self.pid, chatroom_id, announcement)

    def SetChatRoomSelfNickname(self, chatroom_id: str, nickname: str) -> int:
L
ljc545w 已提交
952 953 954 955 956
        """
        设置群内个人昵称

        Parameters
        ----------
L
ljc545w 已提交
957
        chatroom_id : str
L
ljc545w 已提交
958 959 960 961 962 963 964 965 966 967
            群聊id.
        nickname : str
            要修改为的昵称.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
968 969 970
        return self.robot.CSetChatRoomSelfNickname(self.pid, chatroom_id, nickname)

    def GetChatRoomMemberNickname(self, chatroom_id: str, wxid: str) -> str:
L
ljc545w 已提交
971 972 973 974 975
        """
        获取群成员昵称

        Parameters
        ----------
L
ljc545w 已提交
976
        chatroom_id : str
L
ljc545w 已提交
977 978 979 980 981 982 983 984 985 986
            群聊id.
        wxid : str
            群成员wxid.

        Returns
        -------
        str
            成功返回群成员昵称,失败返回空字符串.

        """
L
ljc545w 已提交
987 988 989
        return self.robot.CGetChatRoomMemberNickname(self.pid, chatroom_id, wxid)

    def DelChatRoomMember(self, chatroom_id: str, wxid_list: str or list or tuple) -> str:
L
ljc545w 已提交
990 991 992 993 994
        """
        删除群成员.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
995
        chatroom_id : str
L
ljc545w 已提交
996
            群聊id.
L
ljc545w 已提交
997
        wxid_list : str or list or tuple
L
ljc545w 已提交
998 999 1000 1001 1002 1003 1004 1005
            要删除的成员wxid或wxid列表.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
1006 1007 1008
        return self.robot.CDelChatRoomMember(self.pid, chatroom_id, wxid_list)

    def AddChatRoomMember(self, chatroom_id: str, wxid_list: str or list or tuple) -> str:
L
ljc545w 已提交
1009 1010 1011 1012 1013
        """
        添加群成员.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
1014
        chatroom_id : str
L
ljc545w 已提交
1015
            群聊id.
L
ljc545w 已提交
1016
        wxid_list : str or list or tuple
L
ljc545w 已提交
1017 1018 1019 1020 1021 1022 1023 1024
            要添加的成员wxid或wxid列表.

        Returns
        -------
        int
            成功返回0,失败返回非0值.

        """
L
ljc545w 已提交
1025 1026 1027 1028
        return self.robot.CAddChatRoomMember(self.pid, chatroom_id, wxid_list)


def get_wechat_pid_list() -> list:
L
ljc545w 已提交
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
    """
    获取所有微信pid

    Returns
    -------
    list
        微信pid列表.

    """
    import psutil
L
ljc545w 已提交
1039
    pid_list = []
L
ljc545w 已提交
1040 1041 1042
    process_list = psutil.pids()
    for pid in process_list:
        if psutil.Process(pid).name() == 'WeChat.exe':
L
ljc545w 已提交
1043 1044 1045 1046 1047
            pid_list.append(pid)
    return pid_list


def start_wechat() -> 'WeChatRobot' or None:
L
ljc545w 已提交
1048 1049 1050 1051 1052
    """
    启动微信

    Returns
    -------
L
ljc545w 已提交
1053 1054
    WeChatRobot or None
        成功返回WeChatRobot对象,失败返回None.
L
ljc545w 已提交
1055 1056 1057 1058 1059

    """
    pid = _WeChatRobotClient.instance().robot.CStartWeChat()
    if pid != 0:
        return WeChatRobot(pid)
L
ljc545w 已提交
1060 1061
    return None

L
ljc545w 已提交
1062

1063
def register_msg_event(wx_pid: int, event_sink: 'WeChatEventSink' or None = None) -> None:
L
ljc545w 已提交
1064 1065
    """
    通过COM组件连接点接收消息,真正的回调
1066
    只会收到wx_pid对应的微信消息
L
ljc545w 已提交
1067 1068 1069

    Parameters
    ----------
1070
    wx_pid: 微信PID
L
ljc545w 已提交
1071
    event_sink : object, optional
L
ljc545w 已提交
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081
        回调的实现类,该类要继承`WeChatEventSink`类或实现其中的方法.

    Returns
    -------
    None
        .

    """
    event = _WeChatRobotClient.instance().event
    if event is not None:
L
ljc545w 已提交
1082 1083 1084
        sink = event_sink or WeChatEventSink()
        connection_point = GetEvents(event, sink)
        assert connection_point is not None
1085
        event.CRegisterWxPidWithCookie(wx_pid, connection_point.cookie)
L
ljc545w 已提交
1086 1087 1088
        while True:
            try:
                PumpEvents(2)
L
ljc545w 已提交
1089
            except KeyboardInterrupt:
L
ljc545w 已提交
1090
                break
L
ljc545w 已提交
1091 1092 1093 1094 1095 1096
        del connection_point


def start_socket_server(port: int = 10808,
                        request_handler: 'ReceiveMsgBaseServer' = ReceiveMsgBaseServer,
                        main_thread=True) -> int or None:
L
ljc545w 已提交
1097 1098 1099 1100 1101 1102 1103 1104
    """
    创建消息监听线程

    Parameters
    ----------
    port : int
        socket的监听端口号.
        
L
ljc545w 已提交
1105 1106
    request_handler : ReceiveMsgBaseServer
        用于处理消息的类,需要继承自socketserver.BaseRequestHandler或ReceiveMsgBaseServer
L
ljc545w 已提交
1107
        
L
ljc545w 已提交
1108
    main_thread : bool
L
ljc545w 已提交
1109 1110 1111 1112 1113
        是否在主线程中启动server

    Returns
    -------
    int or None
L
ljc545w 已提交
1114
        main_thread为False时返回线程id,否则返回None.
L
ljc545w 已提交
1115 1116

    """
L
ljc545w 已提交
1117
    ip_port = ("127.0.0.1", port)
L
ljc545w 已提交
1118
    try:
L
ljc545w 已提交
1119 1120
        s = socketserver.ThreadingTCPServer(ip_port, request_handler)
        if main_thread:
L
ljc545w 已提交
1121 1122
            s.serve_forever()
        else:
L
ljc545w 已提交
1123
            socket_server = threading.Thread(target=s.serve_forever)
L
ljc545w 已提交
1124 1125 1126 1127 1128 1129 1130 1131
            socket_server.setDaemon(True)
            socket_server.start()
            return socket_server.ident
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(e)
    return None
L
ljc545w 已提交
1132 1133 1134


def stop_socket_server(thread_id: int) -> None:
L
ljc545w 已提交
1135 1136 1137 1138 1139
    """
    强制结束消息监听线程

    Parameters
    ----------
L
ljc545w 已提交
1140
    thread_id : int
L
ljc545w 已提交
1141 1142 1143 1144 1145 1146 1147 1148
        消息监听线程ID.

    Returns
    -------
    None
        .

    """
L
ljc545w 已提交
1149
    if not thread_id:
L
ljc545w 已提交
1150 1151 1152
        return
    import inspect
    try:
L
ljc545w 已提交
1153 1154
        tid = comtypes.c_long(thread_id)
        res = 0
L
ljc545w 已提交
1155
        if not inspect.isclass(SystemExit):
L
ljc545w 已提交
1156 1157
            exec_type = type(SystemExit)
            res = comtypes.pythonapi.PyThreadState_SetAsyncExc(tid, comtypes.py_object(exec_type))
L
ljc545w 已提交
1158 1159 1160
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
L
ljc545w 已提交
1161
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
L
ljc545w 已提交
1162
            raise SystemError("PyThreadState_SetAsyncExc failed")
L
ljc545w 已提交
1163 1164
    except (ValueError, SystemError):
        pass