├── LICENSE ├── README.md ├── qq.py └── wechat.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 VXenomac 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macOS-QQ-WeChat-API 2 | 3 | ## 什么是 macOS-QQ-WeChat-API? 4 | 5 | 本项目由两部分组成:macOS-QQ-API 以及 macOS-WeChat-API。 6 | 7 | macOS-QQ-API 是基于 [QQPlugin-macOS ](https://github.com/TKkk-iOSer/QQPlugin-macOS)的使用封装,QQPlugin-macOS 使用`GCDWebServer`在本地`127.0.0.1`监听`52777`端口作为 Web 服务器,用于获取用户好友、获取聊天记录、打开与指定好友的聊天窗口、对指定好友发送任意消息。 8 | 9 | macOS-WeChat-API 是基于 [WeChatPlugin-MacOS](https://github.com/TKkk-iOSer/WeChatPlugin-MacOS) 的使用封装,WeChatPlugin-MacOS 使用`GCDWebServer`在本地`127.0.0.1`监听`52700`端口作为 Web 服务器,用于获取用户好友、获取聊天记录、打开与指定好友的聊天窗口、对指定好友发送任意消息。 10 | 11 | ## 使用场景 12 | 13 | Telegram、Slack、Discord、飞书等应用中的机器人扩展了工具的边界,提升了效率,但QQ、微信官方始终不支持微信机器人。QQ、微信已经成为日常通讯信息最重要的入口,直接发送通知消息至 QQ、微信将会降低工具切换之间带来的消息延迟。为此,本工具基于 TK 大佬几年前开发的插件进行使用封装,可在包括但不局限于以下场景中使用: 14 | 15 | 1. 给指定的人在指定的时间发送指定的消息 16 | 2. 给群组推送加密货币最新价格、价格变动情况 17 | 3. 群发消息 18 | 4. …… 19 | 20 | ## 通用使用限制 21 | 22 | 1. macOS 23 | 2. 需要安装 [Alfred](https://www.alfredapp.com) 24 | 25 | ## macOS-QQ-API 使用限制 26 | 27 | 1. 需要安装 [QQPlugin-macOS ](https://github.com/TKkk-iOSer/QQPlugin-macOS) 28 | 2. 需要安装 [QQ-alfred-workflow](https://github.com/TKkk-iOSer/QQPlugin-macOS/blob/master/Other/QQ%20Plugin.alfredworkflow) 29 | 3. 在 QQ 插件中打开`开启 alfred` 30 | 31 | ## macOS-WeChat-API 使用限制 32 | 33 | 1. 需要安装[WeChatExtension-ForMac](https://github.com/MustangYM/WeChatExtension-ForMac) 34 | 2. 需要安装[wechat-alfred-workflow](https://github.com/TKkk-iOSer/wechat-alfred-workflow) 35 | 3. 在微信插件中打开`小助手`->`开启 Alfred 功能` 36 | 37 | 38 | 39 | ## macOS-QQ-API 使用方法 40 | 41 | 1. 初始化 QQ 42 | 43 | ```python 44 | from qq import QQ 45 | 46 | 47 | qq = QQ() 48 | ``` 49 | 50 | 2. 搜索显示名称为狗狗币的联系人/群组 51 | 52 | ```python 53 | qq.search_user_by_name('狗狗币') 54 | ``` 55 | 56 | 3. 返回与狗狗币最近的五条聊天记录 57 | 58 | ```python 59 | qq.get_chat_log_by_name('狗狗币', 1) 60 | ``` 61 | 62 | 4. 返回与 userId 为 DogeCoin 联系人的最近的五条聊天记录 63 | 64 | ```python 65 | qq.get_chat_log_by_id('DogeCoin', 1) 66 | ``` 67 | 68 | 5. 返回与 userId 为 DogeCoin 群组的最近的五条聊天记录 69 | 70 | ```python 71 | qq.get_chat_log_by_id('DogeCoin', 101) 72 | ``` 73 | 74 | 6. 给显示名称为汪汪汪的联系人发送消息 75 | 76 | ```python 77 | qq.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊', 1) 78 | ``` 79 | 80 | 7. 给显示名称为汪汪汪的群组发送消息 81 | 82 | ```python 83 | qq.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊', 101) 84 | ``` 85 | 86 | 8. 给 userId 为 DogeCoin 和 ElonMusk 的联系人发送消息 87 | 88 | ```python 89 | qq.send_message_by_ids( 90 | ['DogeCoin', 'ElonMusk'], 'Everything to the moon!', 1) 91 | ``` 92 | 93 | 9. 给显示名称为汪汪汪的群组发送消息 94 | 95 | ```python 96 | qq.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊', 101) 97 | ``` 98 | 99 | ## macOS-WeChat-API 使用方法 100 | 101 | 1. 初始化 WeChat 102 | 103 | ```python 104 | from wechat import WeChat 105 | 106 | 107 | wechat = WeChat() 108 | ``` 109 | 110 | 2. 搜索显示名称为狗狗币的联系人/群组(速度较慢) 111 | 112 | ```python 113 | wechat.search_user_by_keyword('狗狗币') 114 | ``` 115 | 116 | 3. 搜索显示名称为狗狗币的联系人/群组(速度较快) 117 | 118 | ```python 119 | wechat.search_user_by_name('狗狗币') 120 | ``` 121 | 122 | 4. 返回与狗狗币最近的五条聊天记录 123 | 124 | ```python 125 | wechat.get_chat_log_by_name('狗狗币', 5) 126 | ``` 127 | 128 | 5. 返回与 userId 为 dogecoin 的最近的五条聊天记录 129 | 130 | ```python 131 | wechat.get_chat_log_by_id('DogeCoin', 5) 132 | ``` 133 | 134 | 6. 给显示名称为汪汪汪的联系人/群组发送消息 135 | 136 | ```python 137 | wechat.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊') 138 | ``` 139 | 140 | 7. 给 userId 为 DogeCoin 和 ElonMusk 的联系人/群组发送消息 141 | 142 | ```python 143 | wechat.send_message_by_ids(['DogeCoin', 'ElonMusk'], 'Everything to the moon!') 144 | ``` 145 | 146 | -------------------------------------------------------------------------------- /qq.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class QQ: 5 | 6 | def __init__(self): 7 | self.BASE_URL = 'http://127.0.0.1:52777/QQ-plugin/' 8 | # [GET] 最近聊天列表,可选参数: [keyword] 9 | self.USER_URL = self.BASE_URL + 'user' 10 | # [GET] 根据用户 id 查询指定数量聊天记录,可选参数:[userId, count] 11 | self.CHAT_LOG_URL = self.BASE_URL + 'chatlog' 12 | # [POST] 根据用户 id 发送消息,可选参数:[userId, content, srvId](Content-Type: application/x-www-form-urlencoded) 13 | self.SEND_MESSAGE_URL = self.BASE_URL + 'send-message' 14 | # [POST] 打开与指定好友的聊天窗口,参数:[userId](Content-Type: application/x-www-form-urlencoded) 15 | self.OPEN_SESSION_URL = self.BASE_URL + 'open-session' 16 | 17 | def _get_user_id_by_name(self, users, name): 18 | """根据名称获取 userId 19 | 20 | Args: 21 | users (list): 用户字典列表 22 | name (str): 待匹配的名称 23 | 24 | Return: 25 | list: 待匹配名称对应 userId 26 | """ 27 | return [ 28 | user['userId'] 29 | for user in users 30 | if name == user['title'].replace('[群聊]', '').split('(')[0] 31 | ] 32 | 33 | def search_user_by_name(self, name, group=False): 34 | """根据名称获取 userId 35 | 36 | Args: 37 | name (str): 待匹配的名称 38 | group (bool, optional): 是否多选,默认为 False,若 False 在匹配多个情况下默认返回最近的联系人 39 | 40 | Returns: 41 | list: userId 列表 42 | """ 43 | r = requests.get(self.USER_URL).json() 44 | user_ids = self._get_user_id_by_name(r, name) 45 | if not user_ids: 46 | return [] 47 | if group: 48 | return user_ids 49 | else: 50 | return [user_ids[0]] 51 | 52 | def get_chat_log_by_name(self, name, user_type=1): 53 | """根据待匹配名称获取聊天记录 54 | 55 | Args: 56 | name (str): 用户 userId 57 | user_type (int, optional): 用户类型,默认为 1,用户为 1,群聊为 101 58 | 59 | Raises: 60 | ValueError: 待匹配名称重复,例如有两个相同的人名 61 | 62 | Returns: 63 | dict: 聊天记录字典 64 | """ 65 | user_ids = self.search_user_by_name(name, group=True) 66 | if len(user_ids) > 1: 67 | raise ValueError("用户 ID 数量大于 1,请检查是否同名") 68 | r = requests.get(self.CHAT_LOG_URL, params={ 69 | "userId": user_ids, "type": user_type}) 70 | if r: 71 | return {i['subTitle']: i['title'] for i in r.json()[:1:-1]} 72 | else: 73 | return {} 74 | 75 | def get_chat_log_by_id(self, user_id, user_type=1): 76 | """根据 userId 获取聊天记录 77 | 78 | Args: 79 | user_id (int): 用户 userId 80 | user_type (int, optional): 用户类型,默认为 1,用户为 1,群聊为 101 81 | 82 | Returns: 83 | dict: 聊天记录字典 84 | """ 85 | r = requests.get(self.CHAT_LOG_URL, params={ 86 | "userId": user_id, "type": user_type}) 87 | if r: 88 | return {i['subTitle']: i['title'] for i in r.json()[:1:-1]} 89 | else: 90 | return {} 91 | 92 | def send_message_by_name(self, name, content, user_type=1, group=False): 93 | """通过待匹配名称发送消息 94 | 95 | Args: 96 | name ([type]): [description] 97 | content ([type]): [description] 98 | user_type (int, optional): 用户类型,默认为 1,用户为 1,群聊为 101 99 | group (bool, optional): 是否群发,默认 False 100 | 101 | Returns: 102 | bool: 布尔值 103 | """ 104 | headers = { 105 | "Content-Type": "application/x-www-form-urlencoded", 106 | } 107 | user_ids = self.search_user_by_name(name, group=group) 108 | for user_id in user_ids: 109 | r = requests.post(self.SEND_MESSAGE_URL, headers=headers, data={ 110 | "userId": user_id, "content": content, "type": user_type}) 111 | return True 112 | 113 | def send_message_by_ids(self, user_ids, content, user_type=1): 114 | """通过 userId 发送消息 115 | 116 | Args: 117 | user_ids (list): 需要发送消息的 userId 列表 118 | content (str): 待发送的消息 119 | user_type (int, optional): 用户类型,默认为 1,用户为 1,群聊为 101 120 | 121 | Returns: 122 | bool: 布尔值 123 | """ 124 | headers = { 125 | "Content-Type": "application/x-www-form-urlencoded", 126 | } 127 | for user_id in user_ids: 128 | r = requests.post(self.SEND_MESSAGE_URL, headers=headers, data={ 129 | "userId": user_id, "content": content, "type": user_type}) 130 | return True 131 | 132 | 133 | if __name__ == '__main__': 134 | qq = QQ() 135 | print(qq.search_user_by_name('狗狗币')) # 搜索显示名称为狗狗币的联系人/群组 136 | print(qq.get_chat_log_by_name('狗狗币', 1)) # 返回与狗狗币最近的五条聊天记录 137 | # 返回与 userId 为 DogeCoin 联系人的最近的五条聊天记录 138 | print(qq.get_chat_log_by_id('DogeCoin', 1)) 139 | # 返回与 userId 为 DogeCoin 群组的最近的五条聊天记录 140 | print(qq.get_chat_log_by_id('DogeCoin', 101)) 141 | # 给显示名称为汪汪汪的联系人发送消息 142 | print(qq.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊', 1)) 143 | # 给显示名称为汪汪汪的群组发送消息 144 | print(qq.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊', 101)) 145 | # 给 userId 为 DogeCoin 和 ElonMusk 的联系人发送消息 146 | print(qq.send_message_by_ids( 147 | ['DogeCoin', 'ElonMusk'], 'Everything to the moon!', 1)) 148 | # 给 userId 为 DogeCoin 和 ElonMusk 的群组发送消息 149 | print(qq.send_message_by_ids( 150 | ['DogeCoin', 'ElonMusk'], 'Everything to the moon!', 101)) 151 | -------------------------------------------------------------------------------- /wechat.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class WeChat: 5 | 6 | def __init__(self): 7 | self.BASE_URL = 'http://127.0.0.1:52700/wechat-plugin/' 8 | # [GET] 最近聊天列表,可选参数: [keyword] 9 | self.USER_URL = self.BASE_URL + 'user' 10 | # [GET] 根据用户 id 查询指定数量聊天记录,可选参数:[userId, count] 11 | self.CHAT_LOG_URL = self.BASE_URL + 'chatlog' 12 | # [POST] 根据用户 id 发送消息,可选参数:[userId, content, srvId](Content-Type: application/x-www-form-urlencoded) 13 | self.SEND_MESSAGE_URL = self.BASE_URL + 'send-message' 14 | # [POST] 打开与指定好友的聊天窗口,参数:[userId](Content-Type: application/x-www-form-urlencoded) 15 | self.OPEN_SESSION_URL = self.BASE_URL + 'open-session' 16 | 17 | def _get_user_id_by_name(self, users, name): 18 | """根据名称获取 userId 19 | 20 | Args: 21 | users (list): 用户字典列表 22 | name (str): 待匹配的名称 23 | 24 | Return: 25 | list: 待匹配名称对应 userId 26 | """ 27 | return [ 28 | user['userId'] 29 | for user in users 30 | if name == user['title'].replace('[群聊]', '').split('(')[0] 31 | ] 32 | 33 | def search_user_by_keyword(self, keyword): 34 | """根据关键词搜索用户 35 | 36 | Args: 37 | keyword (str): 关键词 38 | 39 | Returns: 40 | dict: 用户相关信息列表 41 | """ 42 | return requests.get(self.USER_URL, params={"keyword": keyword}).json() 43 | 44 | def search_user_by_name(self, name, group=False): 45 | """根据名称获取 userId 46 | 47 | Args: 48 | name (str): 待匹配的名称 49 | group (bool, optional): 是否多选,默认为 False,若 False 在匹配多个情况下默认返回最近的联系人 50 | 51 | Returns: 52 | list: userId 列表 53 | """ 54 | r = requests.get(self.USER_URL).json() 55 | user_ids = self._get_user_id_by_name(r, name) 56 | if not user_ids: 57 | return [] 58 | if group: 59 | return user_ids 60 | else: 61 | return [user_ids[0]] 62 | 63 | def get_chat_log_by_name(self, name, count): 64 | """根据待匹配名称获取聊天记录 65 | 66 | Args: 67 | name (str): 用户 userId 68 | count (int): 返回聊天记录条目数 69 | 70 | Raises: 71 | ValueError: 待匹配名称重复,例如有两个相同的人名 72 | 73 | Returns: 74 | dict: 聊天记录字典 75 | """ 76 | user_ids = self.search_user_by_name(name, group=True) 77 | if len(user_ids) > 1: 78 | raise ValueError("用户 ID 数量大于 1,请检查是否同名") 79 | r = requests.get(self.CHAT_LOG_URL, params={ 80 | "userId": user_ids, "count": count}) 81 | if r: 82 | return {i['subTitle']: i['copyText'] for i in r.json()[:1:-1]} 83 | else: 84 | return {} 85 | 86 | def get_chat_log_by_id(self, user_id, count): 87 | """根据 userId 获取聊天记录 88 | 89 | Args: 90 | user_id (int): 用户 userId 91 | count (int): 返回聊天记录条目数 92 | 93 | Returns: 94 | dict: 聊天记录字典 95 | """ 96 | r = requests.get(self.CHAT_LOG_URL, params={ 97 | "userId": user_id, "count": count}) 98 | if r: 99 | return {i['subTitle']: i['copyText'] for i in r.json()[:1:-1]} 100 | else: 101 | return {} 102 | 103 | def send_message_by_name(self, name, content, srvId=1, group=False): 104 | """通过待匹配名称发送消息 105 | 106 | Args: 107 | name (str): 待匹配名称 108 | content (str): 待发送的消息 109 | srvId (int, optional): Defaults to 1. 110 | group (bool, optional): 是否群发,默认 False 111 | 112 | Returns: 113 | bool: 布尔值 114 | """ 115 | headers = { 116 | "Content-Type": "application/x-www-form-urlencoded", 117 | } 118 | user_ids = self.search_user_by_name(name, group=group) 119 | for user_id in user_ids: 120 | r = requests.post(self.SEND_MESSAGE_URL, headers=headers, data={ 121 | "userId": user_id, "content": content, "srvId": srvId}) 122 | return True 123 | 124 | def send_message_by_ids(self, user_ids, content, srvId=1): 125 | """通过 userId 发送消息 126 | 127 | Args: 128 | user_ids (list): 需要发送消息的 userId 列表 129 | content (str): 待发送的消息 130 | srvId (int, optional): [int]. Defaults to 1. 131 | 132 | Returns: 133 | bool: 布尔值 134 | """ 135 | headers = { 136 | "Content-Type": "application/x-www-form-urlencoded", 137 | } 138 | for user_id in user_ids: 139 | r = requests.post(self.SEND_MESSAGE_URL, headers=headers, data={ 140 | "userId": user_id, "content": content, "srvId": srvId}) 141 | return True 142 | 143 | 144 | if __name__ == '__main__': 145 | wechat = WeChat() 146 | print(wechat.search_user_by_keyword('狗狗币')) # 搜索显示名称为狗狗币的联系人/群组(速度较慢) 147 | print(wechat.search_user_by_name('狗狗币')) # 搜索显示名称为狗狗币的联系人/群组(速度较快) 148 | print(wechat.get_chat_log_by_name('狗狗币', 5)) # 返回与狗狗币最近的五条聊天记录 149 | # 返回与 userId 为 dogecoin 的最近的五条聊天记录 150 | print(wechat.get_chat_log_by_id('DogeCoin', 5)) 151 | # 给显示名称为汪汪汪的联系人/群组发送消息 152 | print(wechat.send_message_by_name('汪汪汪', '哟,这不狗狗币么,几天不见,这么拉了啊')) 153 | # 给 userId 为 DogeCoin 和 ElonMusk 的联系人/群组发送消息 154 | print(wechat.send_message_by_ids( 155 | ['DogeCoin', 'ElonMusk'], 'Everything to the moon!')) 156 | --------------------------------------------------------------------------------