wxRobot.py 28.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
L
ljc545w 已提交
9
import os
L
ljc545w 已提交
10
import ctypes
11 12
import json
import base64
L
ljc545w 已提交
13
import ctypes.wintypes
L
ljc545w 已提交
14
import socketserver
15
import threading
L
ljc545w 已提交
16 17
# need `pip install comtypes`
import comtypes.client
L
ljc545w 已提交
18 19
from comtypes.client import GetEvents
from comtypes.client import PumpEvents
L
ljc545w 已提交
20

L
ljc545w 已提交
21 22

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

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

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

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

L
ljc545w 已提交
47 48

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

    def OnGetMessageEvent(self, msg):
55 56
        msg = json.loads(msg)
        msg['extrainfo'] = base64.b64decode(msg['extrainfo'])
L
ljc545w 已提交
57
        print(msg)
L
ljc545w 已提交
58

L
ljc545w 已提交
59 60 61 62 63

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

L
ljc545w 已提交
64 65 66 67 68
    def handle(self):
        conn = self.request
        comtypes.CoInitialize()
        while True:
            try:
69 70
                ptr_data = b""
                while True:
L
ljc545w 已提交
71
                    data = conn.recv(1024)
L
ljc545w 已提交
72
                    ptr_data += data
73 74 75 76
                    if len(data) == 0 or data[-1] == 0xA:
                        break
                msg = json.loads(ptr_data.decode('utf-8'))
                ReceiveMsgBaseServer.msg_callback(msg)
L
ljc545w 已提交
77 78
            except OSError:
                break
79 80 81
            except json.JSONDecodeError:
                pass
            conn.sendall("200 OK".encode())
L
ljc545w 已提交
82 83
        conn.close()
        comtypes.CoUninitialize()
L
ljc545w 已提交
84 85

    @staticmethod
86 87
    def msg_callback(msg):
        msg['extrainfo'] = base64.b64decode(msg['extrainfo'])
L
ljc545w 已提交
88
        # 主线程中已经注入,此处禁止调用StartService和StopService
L
ljc545w 已提交
89 90
        robot = comtypes.client.CreateObject("WeChatRobot.CWeChatRobot")
        event = comtypes.client.CreateObject("WeChatRobot.RobotEvent")
91 92
        wx = WeChatRobot(msg['pid'], robot, event)
        userinfo = wx.GetWxUserInfo(msg['wxid'])
L
ljc545w 已提交
93
        msg['alias'] = userinfo['wxNumber']
94 95 96
        if msg['isSendMsg'] == 0:
            if '@chatroom' in msg['sender']:
                chatroom_info = wx.GetWxUserInfo(msg['sender'])
L
ljc545w 已提交
97
                msg['chatroom_name'] = chatroom_info['wxNickName']
98
                msg['nickname'] = wx.GetChatRoomMemberNickname(msg['sender'], msg['wxid'])
L
ljc545w 已提交
99 100 101
            else:
                msg['nickname'] = userinfo['wxNickName']
        # TODO: 在这里写额外的消息处理逻辑
L
ljc545w 已提交
102

L
ljc545w 已提交
103
        print(msg)
L
ljc545w 已提交
104 105
        robot.Release()
        event.Release()
106

L
ljc545w 已提交
107 108 109

class ChatSession:
    def __init__(self, pid, robot, wxid):
L
ljc545w 已提交
110
        self.pid = pid
111
        self.robot = robot
L
ljc545w 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
        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 已提交
134
            return 1
L
ljc545w 已提交
135 136 137 138 139
        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 已提交
140

L
ljc545w 已提交
141
class WeChatRobot:
142

L
ljc545w 已提交
143
    def __init__(self, pid: int = 0, robot=None, event=None):
L
ljc545w 已提交
144 145 146
        self.pid = pid
        self.robot = robot or _WeChatRobotClient.instance().robot
        self.event = event or _WeChatRobotClient.instance().event
147
        self.AddressBook = []
L
ljc545w 已提交
148

L
ljc545w 已提交
149 150 151 152 153 154 155 156 157 158
    def StartService(self) -> int:
        """
        注入DLL到微信以启动服务

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

        """
L
ljc545w 已提交
159
        status = self.robot.CStartRobotService(self.pid)
160
        return status
L
ljc545w 已提交
161

L
ljc545w 已提交
162 163 164 165 166 167 168 169 170 171
    def IsWxLogin(self) -> int:
        """
        获取微信登录状态

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

        """
L
ljc545w 已提交
172
        return self.robot.CIsWxLogin(self.pid)
173

L
ljc545w 已提交
174
    def SendText(self, receiver: str, msg: str) -> int:
L
ljc545w 已提交
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        """
        发送文本消息

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

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

        """
L
ljc545w 已提交
191 192 193
        return self.robot.CSendText(self.pid, receiver, msg)

    def SendImage(self, receiver: str, img_path: str) -> int:
L
ljc545w 已提交
194 195 196 197 198 199 200
        """
        发送图片消息

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
L
ljc545w 已提交
201
        img_path : str
L
ljc545w 已提交
202 203 204 205 206 207 208 209
            图片绝对路径.

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

        """
L
ljc545w 已提交
210 211 212
        return self.robot.CSendImage(self.pid, receiver, img_path)

    def SendFile(self, receiver: str, filepath: str) -> int:
L
ljc545w 已提交
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
        """
        发送文件

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

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

        """
L
ljc545w 已提交
229 230 231
        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 已提交
232 233 234 235 236 237 238 239 240 241 242 243 244
        """
        发送XML文章

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
        title : str
            消息卡片标题.
        abstract : str
            消息卡片摘要.
        url : str
            文章链接.
L
ljc545w 已提交
245
        img_path : str or None, optional
L
ljc545w 已提交
246 247 248 249 250 251 252 253
            消息卡片显示的图片绝对路径,不需要可以不指定. The default is None.

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

        """
L
ljc545w 已提交
254 255 256
        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 已提交
257 258 259 260 261 262 263
        """
        发送名片

        Parameters
        ----------
        receiver : str
            消息接收者wxid.
L
ljc545w 已提交
264
        shared_wxid : str
L
ljc545w 已提交
265 266 267 268 269 270 271 272 273 274
            被分享人wxid.
        nickname : str
            名片显示的昵称.

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

        """
L
ljc545w 已提交
275 276 277
        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 已提交
278 279 280 281 282
        """
        发送群艾特消息,艾特所有人可以将AtUsers设置为`notify@all`
        无目标群管理权限请勿使用艾特所有人
        Parameters
        ----------
L
ljc545w 已提交
283
        chatroom_id : str
L
ljc545w 已提交
284
            群聊ID.
L
ljc545w 已提交
285
        at_users : list or str or tuple
L
ljc545w 已提交
286 287 288
            被艾特的人列表.
        msg : str
            消息内容.
L
ljc545w 已提交
289
        auto_nickname : bool, optional
L
ljc545w 已提交
290 291 292 293 294 295 296 297
            是否自动填充被艾特人昵称. 默认自动填充.

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

        """
L
ljc545w 已提交
298
        if '@chatroom' not in chatroom_id:
L
ljc545w 已提交
299
            return 1
L
ljc545w 已提交
300
        return self.robot.CSendAtText(self.pid, chatroom_id, at_users, msg, auto_nickname)
L
ljc545w 已提交
301

L
ljc545w 已提交
302 303 304 305 306 307 308 309 310 311
    def GetSelfInfo(self) -> dict:
        """
        获取个人信息

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

        """
312 313
        self_info = self.robot.CGetSelfInfo(self.pid)
        return json.loads(self_info)
L
ljc545w 已提交
314

L
ljc545w 已提交
315 316
    def StopService(self) -> int:
        """
L
ljc545w 已提交
317
        停止服务,会将DLL从微信进程中卸载
L
ljc545w 已提交
318 319 320 321 322 323 324

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

        """
L
ljc545w 已提交
325 326 327
        com_pid = self.robot.CStopRobotService(self.pid)
        return com_pid

L
ljc545w 已提交
328 329 330 331 332 333 334 335 336 337
    def GetAddressBook(self) -> list:
        """
        获取联系人列表

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

        """
L
ljc545w 已提交
338
        try:
L
ljc545w 已提交
339 340
            friend_tuple = self.robot.CGetFriendList(self.pid)
            self.AddressBook = [dict(i) for i in list(friend_tuple)]
L
ljc545w 已提交
341 342
        except IndexError:
            self.AddressBook = []
343
        return self.AddressBook
L
ljc545w 已提交
344

L
ljc545w 已提交
345 346 347 348 349 350 351 352 353 354
    def GetFriendList(self) -> list:
        """
        从通讯录列表中筛选出好友列表

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

        """
355 356
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
357
        friend_list = []
358 359
        for item in self.AddressBook:
            if 'wxid_' == item['wxid'][0:5]:
L
ljc545w 已提交
360 361 362
                friend_list.append(item)
        return friend_list

L
ljc545w 已提交
363 364 365 366 367 368 369 370 371 372
    def GetChatRoomList(self) -> list:
        """
        从通讯录列表中筛选出群聊列表

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

        """
373 374
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
375
        chatroom_list = []
376 377
        for item in self.AddressBook:
            if '@chatroom' in item['wxid']:
L
ljc545w 已提交
378 379 380
                chatroom_list.append(item)
        return chatroom_list

L
ljc545w 已提交
381 382 383 384 385 386 387 388 389 390
    def GetOfficialAccountList(self) -> list:
        """
        从通讯录列表中筛选出公众号列表

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

        """
391 392
        if not self.AddressBook:
            self.GetAddressBook()
L
ljc545w 已提交
393
        official_account_list = []
394 395
        for item in self.AddressBook:
            if 'wxid_' != item['wxid'][0:5] and '@chatroom' not in item['wxid']:
L
ljc545w 已提交
396 397 398 399
                official_account_list.append(item)
        return official_account_list

    def GetFriendByWxRemark(self, remark: str) -> dict or None:
L
ljc545w 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412 413
        """
        通过备注搜索联系人

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

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

        """
414 415 416 417 418 419
        if not self.AddressBook:
            self.GetAddressBook()
        for item in self.AddressBook:
            if item['wxRemark'] == remark:
                return item
        return None
L
ljc545w 已提交
420 421

    def GetFriendByWxNumber(self, wx_number: str) -> dict or None:
L
ljc545w 已提交
422 423 424 425 426
        """
        通过微信号搜索联系人

        Parameters
        ----------
L
ljc545w 已提交
427
        wx_number : str
L
ljc545w 已提交
428 429 430 431 432 433 434 435
            联系人微信号.

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

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

    def GetFriendByWxNickName(self, nickname: str) -> dict or None:
L
ljc545w 已提交
444 445 446 447 448
        """
        通过昵称搜索联系人

        Parameters
        ----------
L
ljc545w 已提交
449
        nickname : str
L
ljc545w 已提交
450 451 452 453 454 455 456 457
            联系人昵称.

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

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

    def GetChatSession(self, wxid: str) -> 'ChatSession':
L
ljc545w 已提交
466 467 468 469 470 471 472 473 474 475 476 477 478 479
        """
        创建一个会话,没太大用处

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

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

        """
L
ljc545w 已提交
480 481 482
        return ChatSession(self.pid, self.robot, wxid)

    def GetWxUserInfo(self, wxid: str) -> dict:
L
ljc545w 已提交
483 484 485 486 487 488 489 490 491 492 493 494 495 496
        """
        通过wxid查询联系人信息

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

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

        """
497 498
        userinfo = self.robot.CGetWxUserInfo(self.pid, wxid)
        return json.loads(userinfo)
L
ljc545w 已提交
499 500

    def GetChatRoomMembers(self, chatroom_id: str) -> dict or None:
L
ljc545w 已提交
501 502 503 504 505
        """
        获取群成员信息

        Parameters
        ----------
L
ljc545w 已提交
506
        chatroom_id : str
L
ljc545w 已提交
507 508 509 510
            群聊id.

        Returns
        -------
L
ljc545w 已提交
511 512
        dict or None
            获取成功返回群成员信息,失败返回None.
L
ljc545w 已提交
513 514

        """
L
ljc545w 已提交
515
        info = dict(self.robot.CGetChatRoomMembers(self.pid, chatroom_id))
L
ljc545w 已提交
516 517 518
        if not info:
            return None
        members = info['members'].split('^G')
L
ljc545w 已提交
519
        data = self.GetWxUserInfo(chatroom_id)
L
ljc545w 已提交
520 521
        data['members'] = []
        for member in members:
L
ljc545w 已提交
522 523
            member_info = self.GetWxUserInfo(member)
            data['members'].append(member_info)
L
ljc545w 已提交
524
        return data
L
ljc545w 已提交
525 526

    def CheckFriendStatus(self, wxid: str) -> int:
L
ljc545w 已提交
527 528 529 530 531 532 533 534 535 536 537 538
        """
        获取好友状态码

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

        Returns
        -------
        int
            0x0: 'Unknown',
539 540
            0xB0:'被删除',
            0xB1:'是好友',
L
ljc545w 已提交
541
            0xB2:'已拉黑',
542
            0xB5:'被拉黑',
L
ljc545w 已提交
543 544

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

L
ljc545w 已提交
547
    # 接收消息的函数
L
ljc545w 已提交
548
    def StartReceiveMessage(self, port: int = 10808) -> int:
L
ljc545w 已提交
549
        """
L
ljc545w 已提交
550
        启动接收消息Hook
L
ljc545w 已提交
551 552 553

        Parameters
        ----------
L
ljc545w 已提交
554
        port : int
L
ljc545w 已提交
555
            socket的监听端口号.如果要使用连接点回调,则将端口号设置为0.
L
ljc545w 已提交
556 557 558 559 560 561 562

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

        """
L
ljc545w 已提交
563
        status = self.robot.CStartReceiveMessage(self.pid, port)
564
        return status
L
ljc545w 已提交
565

L
ljc545w 已提交
566
    def StopReceiveMessage(self) -> int:
L
ljc545w 已提交
567
        """
L
ljc545w 已提交
568
        停止接收消息Hook
L
ljc545w 已提交
569 570 571

        Returns
        -------
L
ljc545w 已提交
572 573
        int
            成功返回0,失败返回非0值.
L
ljc545w 已提交
574 575

        """
L
ljc545w 已提交
576 577
        status = self.robot.CStopReceiveMessage(self.pid)
        return status
L
ljc545w 已提交
578

L
ljc545w 已提交
579 580 581 582 583 584 585 586 587 588
    def GetDbHandles(self) -> dict:
        """
        获取数据库句柄和表信息

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

        """
L
ljc545w 已提交
589 590
        tables_tuple = self.robot.CGetDbHandles(self.pid)
        tables = [dict(i) for i in tables_tuple]
L
ljc545w 已提交
591 592 593 594
        dbs = {}
        for table in tables:
            dbname = table['dbname']
            if dbname not in dbs.keys():
L
ljc545w 已提交
595
                dbs[dbname] = {'Handle': table['Handle'], 'tables': []}
L
ljc545w 已提交
596
            dbs[dbname]['tables'].append(
L
ljc545w 已提交
597
                {'name': table['name'], 'tbl_name': table['tbl_name'],
L
ljc545w 已提交
598
                 'root_page': table['rootpage'], 'sql': table['sql']}
L
ljc545w 已提交
599
            )
L
ljc545w 已提交
600
        return dbs
L
ljc545w 已提交
601 602

    def ExecuteSQL(self, handle: int, sql: str) -> list:
L
ljc545w 已提交
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
        """
        执行SQL

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

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

        """
L
ljc545w 已提交
619
        result = self.robot.CExecuteSQL(self.pid, handle, sql)
L
ljc545w 已提交
620 621
        if len(result) == 0:
            return []
L
ljc545w 已提交
622 623 624 625
        query_list = []
        keys = list(result[0])
        for item in result[1:]:
            query_dict = {}
L
ljc545w 已提交
626
            for key, value in zip(keys, item):
L
ljc545w 已提交
627 628 629
                query_dict[key] = value if not isinstance(value, tuple) else bytes(value)
            query_list.append(query_dict)
        return query_list
L
ljc545w 已提交
630 631

    def BackupSQLiteDB(self, handle: int, filepath: str) -> int:
L
ljc545w 已提交
632 633 634 635 636 637 638
        """
        备份数据库

        Parameters
        ----------
        handle : int
            数据库句柄.
L
ljc545w 已提交
639
        filepath : int
L
ljc545w 已提交
640 641 642 643 644 645 646 647
            备份文件保存位置.

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

        """
L
ljc545w 已提交
648 649 650 651 652 653 654
        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 已提交
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
        """
        通过好友请求

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

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

        """
L
ljc545w 已提交
671 672 673
        return self.robot.CVerifyFriendApply(self.pid, v3, v4)

    def AddFriendByWxid(self, wxid: str, message: str or None) -> int:
L
ljc545w 已提交
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
        """
        wxid加好友

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

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

        """
L
ljc545w 已提交
690 691 692
        return self.robot.CAddFriendByWxid(self.pid, wxid, message)

    def AddFriendByV3(self, v3: str, message: str or None, add_type: int = 0x6) -> int:
L
ljc545w 已提交
693
        """
L
ljc545w 已提交
694 695 696 697
        v3数据加好友

        Parameters
        ----------
L
ljc545w 已提交
698
        v3 : str
L
ljc545w 已提交
699 700 701
            v3数据(encryptUserName).
        message : str or None
            验证信息.
L
ljc545w 已提交
702
        add_type : int
L
ljc545w 已提交
703 704 705 706 707 708 709
            添加方式(来源).手机号: 0xF;微信号: 0x3;QQ号: 0x1;朋友验证消息: 0x6.

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

L
ljc545w 已提交
710
        """
L
ljc545w 已提交
711 712
        return self.robot.CAddFriendByV3(self.pid, v3, message, add_type)

L
ljc545w 已提交
713 714 715 716 717 718 719 720 721 722
    def GetWeChatVer(self) -> str:
        """
        获取微信版本号

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

        """
L
ljc545w 已提交
723
        return self.robot.CGetWeChatVer()
L
ljc545w 已提交
724 725

    def GetUserInfoByNet(self, keyword: str) -> dict or None:
L
ljc545w 已提交
726 727 728 729 730 731 732 733 734 735 736 737 738 739
        """
        网络查询用户信息

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

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

        """
L
ljc545w 已提交
740
        userinfo = self.robot.CSearchContactByNet(self.pid, keyword)
L
ljc545w 已提交
741 742
        if userinfo:
            return dict(userinfo)
L
ljc545w 已提交
743
        return None
L
ljc545w 已提交
744 745

    def AddBrandContact(self, public_id: str) -> int:
L
ljc545w 已提交
746 747 748 749 750
        """
        关注公众号

        Parameters
        ----------
L
ljc545w 已提交
751
        public_id : str
L
ljc545w 已提交
752 753 754 755 756 757 758 759
            公众号id.

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

        """
L
ljc545w 已提交
760 761 762
        return self.robot.CAddBrandContact(self.pid, public_id)

    def ChangeWeChatVer(self, version: str) -> int:
L
ljc545w 已提交
763
        """
L
ljc545w 已提交
764 765 766 767 768 769 770 771 772 773 774 775
        自定义微信版本号,一定程度上防止自动更新

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

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

L
ljc545w 已提交
776
        """
L
ljc545w 已提交
777 778 779
        return self.robot.CChangeWeChatVer(self.pid, version)

    def HookImageMsg(self, save_path: str) -> int:
L
ljc545w 已提交
780 781 782 783 784
        """
        开始Hook未加密图片

        Parameters
        ----------
L
ljc545w 已提交
785
        save_path : str
L
ljc545w 已提交
786 787 788 789 790 791 792 793
            图片保存路径(绝对路径).

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

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

L
ljc545w 已提交
796 797 798 799 800 801 802 803 804 805
    def UnHookImageMsg(self) -> int:
        """
        取消Hook未加密图片

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

        """
L
ljc545w 已提交
806
        return self.robot.CUnHookImageMsg(self.pid)
L
ljc545w 已提交
807 808

    def HookVoiceMsg(self, save_path: str) -> int:
L
ljc545w 已提交
809 810 811 812 813
        """
        开始Hook语音消息

        Parameters
        ----------
L
ljc545w 已提交
814
        save_path : str
L
ljc545w 已提交
815 816 817 818 819 820 821 822
            语音保存路径(绝对路径).

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

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

L
ljc545w 已提交
825 826 827 828 829 830 831 832 833 834
    def UnHookVoiceMsg(self) -> int:
        """
        取消Hook语音消息

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

        """
L
ljc545w 已提交
835
        return self.robot.CUnHookVoiceMsg(self.pid)
L
ljc545w 已提交
836

L
ljc545w 已提交
837
    def DeleteUser(self, wxid: str) -> int:
L
ljc545w 已提交
838 839 840 841 842 843 844 845 846 847 848 849 850 851
        """
        删除好友

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

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

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

L
ljc545w 已提交
854
    def SendAppMsg(self, wxid: str, appid: str) -> int:
L
ljc545w 已提交
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
        """
        发送小程序

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

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

        """
L
ljc545w 已提交
871 872 873
        return self.robot.CSendAppMsg(self.pid, wxid, appid)

    def EditRemark(self, wxid: str, remark: str or None) -> int:
L
ljc545w 已提交
874 875 876 877 878 879
        """
        修改好友或群聊备注

        Parameters
        ----------
        wxid : str
L
ljc545w 已提交
880
            wxid或chatroom_id.
L
ljc545w 已提交
881 882 883 884 885 886 887 888 889
        remark : str or None
            要修改的备注.

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

        """
L
ljc545w 已提交
890 891 892
        return self.robot.CEditRemark(self.pid, wxid, remark)

    def SetChatRoomName(self, chatroom_id: str, name: str) -> int:
L
ljc545w 已提交
893 894 895 896 897
        """
        修改群名称.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
898
        chatroom_id : str
L
ljc545w 已提交
899 900 901 902 903 904 905 906 907 908
            群聊id.
        name : str
            要修改为的群名称.

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

        """
L
ljc545w 已提交
909 910 911
        return self.robot.CSetChatRoomName(self.pid, chatroom_id, name)

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

        Parameters
        ----------
L
ljc545w 已提交
917
        chatroom_id : str
L
ljc545w 已提交
918 919 920 921 922 923 924 925 926 927
            群聊id.
        announcement : str or None
            公告内容.

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

        """
L
ljc545w 已提交
928 929 930
        return self.robot.CSetChatRoomAnnouncement(self.pid, chatroom_id, announcement)

    def SetChatRoomSelfNickname(self, chatroom_id: str, nickname: str) -> int:
L
ljc545w 已提交
931 932 933 934 935
        """
        设置群内个人昵称

        Parameters
        ----------
L
ljc545w 已提交
936
        chatroom_id : str
L
ljc545w 已提交
937 938 939 940 941 942 943 944 945 946
            群聊id.
        nickname : str
            要修改为的昵称.

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

        """
L
ljc545w 已提交
947 948 949
        return self.robot.CSetChatRoomSelfNickname(self.pid, chatroom_id, nickname)

    def GetChatRoomMemberNickname(self, chatroom_id: str, wxid: str) -> str:
L
ljc545w 已提交
950 951 952 953 954
        """
        获取群成员昵称

        Parameters
        ----------
L
ljc545w 已提交
955
        chatroom_id : str
L
ljc545w 已提交
956 957 958 959 960 961 962 963 964 965
            群聊id.
        wxid : str
            群成员wxid.

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

        """
L
ljc545w 已提交
966 967 968
        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 已提交
969 970 971 972 973
        """
        删除群成员.请确认具有相关权限再调用。

        Parameters
        ----------
L
ljc545w 已提交
974
        chatroom_id : str
L
ljc545w 已提交
975
            群聊id.
L
ljc545w 已提交
976
        wxid_list : str or list or tuple
L
ljc545w 已提交
977 978 979 980 981 982 983 984
            要删除的成员wxid或wxid列表.

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

        """
L
ljc545w 已提交
985 986 987
        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 已提交
988 989 990 991 992
        """
        添加群成员.请确认具有相关权限再调用。

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

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

        """
L
ljc545w 已提交
1004 1005 1006 1007
        return self.robot.CAddChatRoomMember(self.pid, chatroom_id, wxid_list)


def get_wechat_pid_list() -> list:
L
ljc545w 已提交
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
    """
    获取所有微信pid

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

    """
    import psutil
L
ljc545w 已提交
1018
    pid_list = []
L
ljc545w 已提交
1019 1020
    process_list = psutil.pids()
    for pid in process_list:
L
ljc545w 已提交
1021 1022 1023 1024
        try:
            if psutil.Process(pid).name() == 'WeChat.exe':
                pid_list.append(pid)
        except psutil.NoSuchProcess:
L
ljc545w 已提交
1025
            pass
L
ljc545w 已提交
1026 1027 1028 1029
    return pid_list


def start_wechat() -> 'WeChatRobot' or None:
L
ljc545w 已提交
1030 1031 1032 1033 1034
    """
    启动微信

    Returns
    -------
L
ljc545w 已提交
1035 1036
    WeChatRobot or None
        成功返回WeChatRobot对象,失败返回None.
L
ljc545w 已提交
1037 1038 1039 1040 1041

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

L
ljc545w 已提交
1044

1045
def register_msg_event(wx_pid: int, event_sink: 'WeChatEventSink' or None = None) -> None:
L
ljc545w 已提交
1046 1047
    """
    通过COM组件连接点接收消息,真正的回调
1048
    只会收到wx_pid对应的微信消息
L
ljc545w 已提交
1049 1050 1051

    Parameters
    ----------
1052
    wx_pid: 微信PID
L
ljc545w 已提交
1053
    event_sink : object, optional
L
ljc545w 已提交
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
        回调的实现类,该类要继承`WeChatEventSink`类或实现其中的方法.

    Returns
    -------
    None
        .

    """
    event = _WeChatRobotClient.instance().event
    if event is not None:
L
ljc545w 已提交
1064 1065 1066
        sink = event_sink or WeChatEventSink()
        connection_point = GetEvents(event, sink)
        assert connection_point is not None
1067
        event.CRegisterWxPidWithCookie(wx_pid, connection_point.cookie)
L
ljc545w 已提交
1068 1069 1070
        while True:
            try:
                PumpEvents(2)
L
ljc545w 已提交
1071
            except KeyboardInterrupt:
L
ljc545w 已提交
1072
                break
L
ljc545w 已提交
1073 1074 1075 1076 1077 1078
        del connection_point


def start_socket_server(port: int = 10808,
                        request_handler: 'ReceiveMsgBaseServer' = ReceiveMsgBaseServer,
                        main_thread=True) -> int or None:
L
ljc545w 已提交
1079 1080 1081 1082 1083 1084 1085
    """
    创建消息监听线程

    Parameters
    ----------
    port : int
        socket的监听端口号.
L
ljc545w 已提交
1086

L
ljc545w 已提交
1087 1088
    request_handler : ReceiveMsgBaseServer
        用于处理消息的类,需要继承自socketserver.BaseRequestHandler或ReceiveMsgBaseServer
L
ljc545w 已提交
1089

L
ljc545w 已提交
1090
    main_thread : bool
L
ljc545w 已提交
1091 1092 1093 1094 1095
        是否在主线程中启动server

    Returns
    -------
    int or None
L
ljc545w 已提交
1096
        main_thread为False时返回线程id,否则返回None.
L
ljc545w 已提交
1097 1098

    """
L
ljc545w 已提交
1099
    ip_port = ("127.0.0.1", port)
L
ljc545w 已提交
1100
    try:
L
ljc545w 已提交
1101 1102
        s = socketserver.ThreadingTCPServer(ip_port, request_handler)
        if main_thread:
L
ljc545w 已提交
1103 1104
            s.serve_forever()
        else:
L
ljc545w 已提交
1105
            socket_server = threading.Thread(target=s.serve_forever)
L
ljc545w 已提交
1106 1107 1108 1109 1110 1111 1112 1113
            socket_server.setDaemon(True)
            socket_server.start()
            return socket_server.ident
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(e)
    return None
L
ljc545w 已提交
1114 1115 1116


def stop_socket_server(thread_id: int) -> None:
L
ljc545w 已提交
1117 1118 1119 1120 1121
    """
    强制结束消息监听线程

    Parameters
    ----------
L
ljc545w 已提交
1122
    thread_id : int
L
ljc545w 已提交
1123 1124 1125 1126 1127 1128 1129 1130
        消息监听线程ID.

    Returns
    -------
    None
        .

    """
L
ljc545w 已提交
1131
    if not thread_id:
L
ljc545w 已提交
1132 1133 1134
        return
    import inspect
    try:
L
ljc545w 已提交
1135 1136
        tid = comtypes.c_long(thread_id)
        res = 0
L
ljc545w 已提交
1137
        if not inspect.isclass(SystemExit):
L
ljc545w 已提交
1138 1139
            exec_type = type(SystemExit)
            res = comtypes.pythonapi.PyThreadState_SetAsyncExc(tid, comtypes.py_object(exec_type))
L
ljc545w 已提交
1140 1141 1142
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
L
ljc545w 已提交
1143
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
L
ljc545w 已提交
1144
            raise SystemError("PyThreadState_SetAsyncExc failed")
L
ljc545w 已提交
1145 1146
    except (ValueError, SystemError):
        pass