├── __init__.py
├── config.json.template
├── README.MD
└── hello_plus.py
/__init__.py:
--------------------------------------------------------------------------------
1 | from .hello_plus import *
2 |
--------------------------------------------------------------------------------
/config.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "group_welc_fixed_msg": {
3 | "群聊1": "群聊1的固定欢迎语",
4 | "群聊2": "群聊2的固定欢迎语"
5 | },
6 |
7 | "group_welc_prompt": "请你随机使用一种风格说一句问候语来欢迎新用户\"{nickname}\"加入群聊。",
8 |
9 | "group_exit_prompt": "请你随机使用一种风格跟其他群用户说他违反规则\"{nickname}\"退出群聊。",
10 |
11 | "patpat_prompt": "请你随机使用一种风格介绍你自己,并告诉用户输入#help可以查看帮助信息。",
12 |
13 | "use_character_desc": false,
14 | "redirect_link":"https://baike.baidu.com/item/welcome/2135227",
15 | "exit_url": "https://baike.baidu.com/item/%E9%80%80%E5%87%BA/28909",
16 | "say_exit": "有缘再见",
17 | "sleep_time": 60,
18 | "auth_token":"12345679",
19 | "welc_text":false,
20 | "group_names":["测试bott","测试机器人"]
21 | }
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # HelloPlus Plugin
2 |
3 | HelloPlus是一个增强版的微信群管理插件,主要提供群聊欢迎、退群提醒等功能(原hello插件增加卡片欢迎)。
4 |
5 | ## 功能特点
6 |
7 | 1. 群聊欢迎
8 | - 支持自定义欢迎消息
9 | - 显示新成员头像和个性签名和加入时间
10 | - 支持自定义欢迎卡片跳转链接
11 |
12 | 2. 退群提醒
13 | - 可配置是否启用退群提醒
14 | - 支持自定义退群提示语
15 |
16 | 3. 基础对话(原hello功能)
17 | - 响应"Hello"命令:回复用户昵称
18 | - 响应"Hi"命令:简单回复"Hi"
19 | - 响应"End"命令:生成"The World"的图片
20 |
21 | ## 配置参数说明
22 |
23 | ### 基础配置参数
24 |
25 | | 参数名 | 类型 | 默认值 | 说明 |
26 | |--------|------|--------|------|
27 | | group_welc_fixed_msg | object | {} | 群固定欢迎语配置,格式为 {"群名": "欢迎语"} |
28 | | group_welc_prompt | string | "请你随机使用一种风格说一句问候语来欢迎新用户{nickname}加入群聊。" | AI欢迎语模板 |
29 | | group_exit_prompt | string | "请你随机使用一种风格介绍你自己,并告诉用户输入#help可以查看帮助信息。" | 退群提示语模板 |
30 | | patpat_prompt | string | "请你随机使用一种风格跟其他群用户说他违反规则{nickname}退出群聊。" | 拍一拍提示语模板 |
31 | | welc_text | boolean | false | 是否在欢迎卡片后追加文字欢迎 |
32 | | use_character_desc | boolean | true | 是否使用角色设定 |
33 |
34 | ### 链接配置参数
35 |
36 | | 参数名 | 类型 | 默认值 | 说明 |
37 | |--------|------|--------|------|
38 | | redirect_link | string | "https://baike.baidu.com/item/welcome/2135227" | 欢迎卡片跳转链接 |
39 | | exit_url | string | "https://baike.baidu.com/item/%E9%80%80%E5%87%BA/28909" | 退群卡片跳转链接 |
40 |
41 | ### 监控配置参数
42 |
43 | | 参数名 | 类型 | 默认值 | 说明 |
44 | |--------|------|--------|------|
45 | | sleep_time | integer | 60 | 群监控检查间隔(秒) |
46 | | auth_token | string | "12345679" | 管理员验证token |
47 | | say_exit | string | "有缘再见" | 退群卡片提示语 |
48 | | group_names | array | [] | 默认开启监控的群列表,格式为 ["群名1", "群名2"] |
49 |
50 | ### 示例配置
51 |
52 | ```json
53 | {
54 | "group_welc_fixed_msg": {
55 | "测试群": "欢迎加入测试群!"
56 | },
57 | "group_welc_prompt": "热烈欢迎{nickname}加入我们的大家庭!",
58 | "redirect_link": "https://example.com/welcome",
59 | "exit_url": "https://example.com/goodbye",
60 | "sleep_time": 30,
61 | "auth_token": "your-secure-token",
62 | "welc_text": true,
63 | "use_character_desc": true
64 | }
65 | ```
66 |
67 | ## 使用方法
68 |
69 | 1. 群成员加入时自动触发欢迎
70 | - 显示欢迎卡片,包含成员昵称、时间、签名等信息(失败使用普通欢迎)
71 | - 如果配置了固定欢迎语,优先使用固定欢迎语
72 |
73 | 2. 群成员退出时自动提醒
74 | - 如果启用了退群提醒,会发送相应消息
75 | - 可在配置中自定义退群提示语
76 |
77 | 3. 基础命令
78 | - 发送"Hello":机器人回复问候语
79 | - 发送"Hi":机器人简单回复
80 | - 发送"End":机器人生成图片
81 | - 发送"#help":查看帮助信息
82 |
83 | 4. 群监控操作
84 | - 发送"开启退群监控":开启当前群的退群监控功能
85 | - 发送"关闭退群监控":关闭当前群的退群监控功能
86 | - 发送"查看监控群列表":查看当前所有开启监控的群及其成员数量
87 | - 发送"群监控管理验证 [token]":验证成为群监控管理员(私聊)
88 | - 私聊发送"开启监控 群聊名":开启指定群的退群监控功能
89 | - 私聊发送"关闭监控 群聊名":关闭指定群的退群监控功能
90 |
91 | 注意:
92 | - 只有管理员可以操作群监控功能
93 | - 监控间隔可通过 sleep_time 参数配置
94 | - 管理员验证token需要在配置文件中设置
95 | - 建议在私聊环境下进行管理员验证
96 | - 私聊操作群监控需要(机器人)保存群到通讯录
97 | - 默认开启监控的群列表需要(机器人)保存群到通讯录
98 |
99 | ## 注意事项
100 |
101 | 1. 请确保已正确配置GE微信相关参数
102 | 2. 建议根据实际需求调整欢迎语和提示语模板
103 | 3. 如需禁用某些功能,可在配置文件中设置相应参数
104 |
105 | ## 作者
106 |
107 | 作者:wangcl
108 | 版本:0.1
109 |
--------------------------------------------------------------------------------
/hello_plus.py:
--------------------------------------------------------------------------------
1 | # encoding:utf-8
2 |
3 | import plugins
4 | from bridge.context import ContextType
5 | from bridge.reply import Reply, ReplyType
6 | from channel.chat_message import ChatMessage
7 | from common.log import logger
8 | from plugins import *
9 | from config import conf
10 |
11 |
12 | @plugins.register(
13 | name="HelloPlus",
14 | desire_priority=1,
15 | hidden=True,
16 | desc="欢迎plus版",
17 | version="0.1",
18 | author="wangcl",
19 | )
20 |
21 |
22 | class HelloPlus(Plugin):
23 |
24 | group_welc_prompt = "请你随机使用一种风格说一句问候语来欢迎新用户\"{nickname}\"加入群聊。"
25 | group_exit_prompt = "请你随机使用一种风格介绍你自己,并告诉用户输入#help可以查看帮助信息。"
26 | patpat_prompt = "请你随机使用一种风格跟其他群用户说他违反规则\"{nickname}\"退出群聊。"
27 | exit_url="https://baike.baidu.com/item/%E9%80%80%E5%87%BA/28909"
28 | redirect_link = "https://baike.baidu.com/item/welcome/2135227"
29 | say_exit = "有缘再见"
30 | sleep_time = 60
31 | welc_text = False
32 | auth_token = "12345679"
33 | admin_user = set()
34 | group_names=['群聊1','群聊2']
35 | ql_list = {}
36 | def __init__(self):
37 | super().__init__()
38 | try:
39 | self.config = super().load_config()
40 | if not self.config:
41 | self.config = self._load_config_template()
42 | self.group_welc_fixed_msg = self.config.get("group_welc_fixed_msg", {})
43 | self.group_welc_prompt = self.config.get("group_welc_prompt", self.group_welc_prompt)
44 | self.group_exit_prompt = self.config.get("group_exit_prompt", self.group_exit_prompt)
45 | self.group_exit_prompt_str="\"{nickname}\"退出群聊。请你随机使用一种风格跟他说再见。"
46 | self.patpat_prompt = self.config.get("patpat_prompt", self.patpat_prompt)
47 | self.redirect_link = self.config.get("redirect_link", self.redirect_link)
48 | self.exit_url = self.config.get("exit_url", self.exit_url)
49 | self.say_exit = self.config.get("say_exit", self.say_exit)
50 | self.sleep_time=self.config.get("sleep_time", self.sleep_time)
51 | self.auth_token=self.config.get("auth_token", self.auth_token)
52 | self.welc_text=self.config.get("welc_text", self.welc_text)
53 | self.group_names=self.config.get("group_names", self.group_names)
54 | self.appid = conf().get("gewechat_app_id", "")
55 | self.gewetk = conf().get("gewechat_token", "")
56 | self.base_url = conf().get("gewechat_base_url")
57 | self.group_members={}
58 | self.memberList = []
59 | self.admin_user = []
60 | self.monitor_threads = {} # 存储监控线程
61 | self.monitoring_groups = set() # 存储正在监控的群组ID
62 | self.monitoring_groups_name = {} # 存储正在监控的群组name
63 | self.headers = {
64 | 'X-GEWE-TOKEN': self.gewetk,
65 | 'Content-Type': 'application/json'
66 | }
67 | try:
68 | self.get_group_list()
69 |
70 | logger.info(f"[HelloPlus]默认群聊监控开启成功")
71 | except Exception as e:
72 | logger.error(f"[HelloPlus]默认群聊监控开启失败:{e}")
73 | logger.info("[HelloPlus] inited")
74 | self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
75 | except Exception as e:
76 | print(f"[HelloPlus]初始化异常-----:{e}")
77 | logger.error(f"[HelloPlus]初始化异常:{e}")
78 | raise "[HelloPlus] init failed, ignore "
79 |
80 | def on_handle_context(self, e_context: EventContext):
81 | if e_context["context"].type not in [
82 | ContextType.TEXT,
83 | ContextType.JOIN_GROUP,
84 | ContextType.PATPAT,
85 | ContextType.EXIT_GROUP
86 | ]:
87 | return
88 | msg: ChatMessage = e_context["context"]["msg"]
89 | group_name = msg.from_user_nickname
90 | if e_context["context"].type == ContextType.JOIN_GROUP:
91 | if "group_welcome_msg" in conf() or group_name in self.group_welc_fixed_msg:
92 |
93 | reply = Reply()
94 | reply.type = ReplyType.TEXT
95 | if group_name in self.group_welc_fixed_msg:
96 | reply.content = self.group_welc_fixed_msg.get(group_name, "")
97 | else:
98 | reply.content = conf().get("group_welcome_msg", "")
99 | e_context["reply"] = reply
100 | e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
101 | return
102 | print('----welcome----')
103 | try:
104 | import time
105 | time.sleep(2)
106 | qm,imgurl,nickName=self.get_info(msg.other_user_id,msg.actual_user_nickname)
107 | if qm!=None or imgurl!=None:
108 | ret=self.welcome(msg,qm,imgurl)
109 | if ret!= 200:
110 | e_context["context"].type = ContextType.TEXT
111 | e_context["context"].content = self.group_welc_prompt.format(nickname=msg.actual_user_nickname)
112 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑
113 | if self.welc_text:
114 | time.sleep(2)
115 | e_context["context"].type = ContextType.TEXT
116 | e_context["context"].content = self.group_welc_prompt.format(nickname=msg.actual_user_nickname)
117 | e_context.action = EventAction.BREAK
118 | else:
119 | e_context["context"].type = ContextType.TEXT
120 | e_context["context"].content = self.group_welc_prompt.format(nickname=msg.actual_user_nickname)
121 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑
122 | except:
123 | e_context["context"].type = ContextType.TEXT
124 | e_context["context"].content = self.group_welc_prompt.format(nickname=msg.actual_user_nickname)
125 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑
126 | if not self.config or not self.config.get("use_character_desc"):
127 | e_context["context"]["generate_breaked_by"] = EventAction.BREAK
128 | return
129 |
130 | if e_context["context"].type == ContextType.EXIT_GROUP:
131 |
132 | if "group_exit_msg" in conf():
133 | reply = Reply()
134 | reply.type = ReplyType.TEXT
135 | reply.content = conf().get("group_exit_msg", "")
136 | e_context["reply"] = reply
137 | e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
138 | return
139 | if conf().get("group_chat_exit_group"):
140 | e_context["context"].type = ContextType.TEXT
141 | e_context["context"].content = self.group_exit_prompt.format(nickname=msg.actual_user_nickname)
142 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑
143 | return
144 | e_context.action = EventAction.BREAK
145 | return
146 |
147 | if e_context["context"].type == ContextType.PATPAT:
148 | e_context["context"].type = ContextType.TEXT
149 | e_context["context"].content = self.patpat_prompt
150 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑
151 | if not self.config or not self.config.get("use_character_desc"):
152 | e_context["context"]["generate_breaked_by"] = EventAction.BREAK
153 | return
154 |
155 | content = e_context["context"].content
156 | logger.debug("[Hello] on_handle_context. content: %s" % content)
157 | if content == "Hello":
158 | reply = Reply()
159 | reply.type = ReplyType.TEXT
160 | if e_context["context"]["isgroup"]:
161 | reply.content = f"Hello, {msg.actual_user_nickname} from {msg.from_user_nickname}"
162 | else:
163 | reply.content = f"Hello, {msg.from_user_nickname}"
164 | e_context["reply"] = reply
165 | e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
166 | user_id=msg.actual_user_id
167 | if content.startswith('群监控管理验证'):
168 | if e_context["context"]["isgroup"]:
169 | reply_cont="不支持群聊验证"
170 | reply = self.create_reply(ReplyType.TEXT, reply_cont)
171 | e_context["reply"] = reply
172 | e_context.action = EventAction.BREAK_PASS
173 | return
174 | tk = content[7:].strip()
175 | reply_cont="验证成功,已将您设为群监控管理员。" if self.add_admin_user(tk,user_id) else "验证失败"
176 | reply = self.create_reply(ReplyType.TEXT, reply_cont)
177 | e_context["reply"] = reply
178 | e_context.action = EventAction.BREAK_PASS
179 | if content =='查看监控群列表':
180 | if not self.is_admin(user_id):
181 | reply = Reply()
182 | reply.type = ReplyType.TEXT
183 | reply.content = "没权限啊"
184 | e_context["reply"] = reply
185 | e_context.action = EventAction.BREAK_PASS
186 | return
187 | # 获取监控群列表
188 | # 从self.group_members获取每个群的成员数量
189 |
190 | if len(self.group_members.keys())==0:
191 | reply = Reply()
192 | reply.type = ReplyType.TEXT
193 | reply.content = "监控群列表为空"
194 | e_context["reply"] = reply
195 | e_context.action = EventAction.BREAK_PASS
196 | return
197 | group_member_count = {}
198 | for group_id, members in self.group_members.items():
199 | group_member_count[group_id] = len(members)
200 | reply = Reply()
201 | reply.type = ReplyType.TEXT
202 | reply.content = "监控群列表:\n"
203 | for group_id in self.monitoring_groups:
204 | reply.content += f" 💬{self.monitoring_groups_name[group_id]} -🙎♂️当前成员: {group_member_count[group_id]}人\n"
205 | e_context["reply"] = reply
206 | e_context.action = EventAction.BREAK_PASS
207 | return
208 | if content.startswith("开启监控"):
209 | if e_context["context"]["isgroup"]:
210 | reply_cont="不支持群聊开启"
211 | reply = self.create_reply(ReplyType.TEXT, reply_cont)
212 | e_context["reply"] = reply
213 | e_context.action = EventAction.BREAK_PASS
214 | return
215 | if not self.is_admin(user_id):
216 | reply = Reply()
217 | reply.type = ReplyType.TEXT
218 | reply.content = "没权限啊"
219 | e_context["reply"] = reply
220 | e_context.action = EventAction.BREAK_PASS
221 | return
222 | group_name=content[4:].strip()
223 | ret,msg=self.start_monitor(group_name)
224 | reply = Reply()
225 | reply.type = ReplyType.TEXT
226 | reply.content = msg
227 | e_context["reply"] = reply
228 | e_context.action = EventAction.BREAK_PASS
229 | if content.startswith("关闭监控"):
230 | group_name=content[4:].strip()
231 | if e_context["context"]["isgroup"]:
232 | reply_cont="不支持群聊关闭"
233 | reply = self.create_reply(ReplyType.TEXT, reply_cont)
234 | e_context["reply"] = reply
235 | e_context.action = EventAction.BREAK_PASS
236 | return
237 | if not self.is_admin(user_id):
238 | reply = Reply()
239 | reply.type = ReplyType.TEXT
240 | reply.content = "没权限啊"
241 | e_context["reply"] = reply
242 | e_context.action = EventAction.BREAK_PASS
243 | return
244 | flag=True
245 | for group_id, name in self.monitoring_groups_name.items():
246 | if name == group_name:
247 | flag=False
248 | if group_id in self.monitoring_groups:
249 | self.monitoring_groups.remove(group_id)
250 | self.monitoring_groups_name.pop(group_id)
251 | reply = Reply()
252 | reply.type = ReplyType.TEXT
253 | reply.content = f"监控关闭成功: {group_name}"
254 | e_context["reply"] = reply
255 | else:
256 | reply = Reply()
257 | reply.type = ReplyType.TEXT
258 | reply.content = f"[{group_name}]未开启退群监控"
259 | e_context["reply"] = reply
260 | break
261 | if flag:
262 | reply = Reply()
263 | reply.type = ReplyType.TEXT
264 | reply.content = f"未找到群组:{group_name}"
265 | e_context["reply"] = reply
266 | e_context.action = EventAction.BREAK_PASS
267 | return
268 | if e_context["context"]["isgroup"]:
269 |
270 | if content =='开启退群监控':
271 | if not self.is_admin(user_id):
272 | reply = Reply()
273 | reply.type = ReplyType.TEXT
274 | reply.content = "没权限啊"
275 | e_context["reply"] = reply
276 | e_context.action = EventAction.BREAK_PASS
277 | return
278 | self.get_member_list(msg.other_user_id,msg.other_user_nickname)
279 | reply = Reply()
280 | reply.type = ReplyType.TEXT
281 | reply.content = f"当前群[{msg.other_user_nickname}]已开启退群监控"
282 | e_context["reply"] = reply
283 | e_context.action = EventAction.BREAK_PASS
284 | return
285 | if content == "关闭退群监控":
286 | if not self.is_admin(user_id):
287 | reply = Reply()
288 | reply.type = ReplyType.TEXT
289 | reply.content = "没权限啊"
290 | e_context["reply"] = reply
291 | e_context.action = EventAction.BREAK_PASS
292 | return
293 | group_id = msg.other_user_id
294 | if group_id in self.monitoring_groups:
295 | self.monitoring_groups.remove(group_id)
296 | self.monitoring_groups_name.pop(group_id)
297 | reply = Reply()
298 | reply.type = ReplyType.TEXT
299 | reply.content = f"当前群[{msg.other_user_nickname}]已关闭退群监控"
300 | e_context["reply"] = reply
301 | else:
302 | reply = Reply()
303 | reply.type = ReplyType.TEXT
304 | reply.content = "当前群未开启退群监控"
305 | e_context["reply"] = reply
306 | e_context.action = EventAction.BREAK_PASS
307 | return
308 | if content == "Hi":
309 | reply = Reply()
310 | reply.type = ReplyType.TEXT
311 | reply.content = "Hi"
312 | e_context["reply"] = reply
313 | e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑,一般会覆写reply
314 |
315 | if content == "End":
316 | # 如果是文本消息"End",将请求转换成"IMAGE_CREATE",并将content设置为"The World"
317 | e_context["context"].type = ContextType.IMAGE_CREATE
318 | content = "The World"
319 | e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
320 |
321 | def get_help_text(self, **kwargs):
322 | help_text = "输入Hello,我会回复你的名字\n输入End,我会回复你世界的图片\n"
323 | return help_text
324 |
325 | def _load_config_template(self):
326 | logger.debug("No Hello plugin config.json, use plugins/hello/config.json.template")
327 | try:
328 | plugin_config_path = os.path.join(self.path, "config.json.template")
329 | if os.path.exists(plugin_config_path):
330 | with open(plugin_config_path, "r", encoding="utf-8") as f:
331 | plugin_conf = json.load(f)
332 | return plugin_conf
333 | except Exception as e:
334 | logger.exception(e)
335 | def welcome(self,msg,qm,imgurl):
336 | import requests
337 | import json
338 | from datetime import datetime
339 | post_url = f"{self.base_url}/message/postAppMsg"
340 | now = datetime.now().strftime("%Y年%m月%d日 %H时%M分%S秒")
341 | url=self.redirect_link
342 | payload = json.dumps({
343 | "appId": self.appid,
344 | "toWxid": msg.other_user_id,
345 | "appmsg": (
346 | f''
347 | f'👏欢迎 {msg.actual_user_nickname} 加入群聊!🎉'
348 | f'⌚:{now}\n签名:{qm if qm else "这个人没有签名"}'
349 | f'view50'
350 | f'{url}'
351 | f''
352 | f'{imgurl}'
353 | ''
354 | '0'
355 | ''
356 | '0'
357 | '0'
358 | ''
359 | )
360 | })
361 | response = requests.request("POST", post_url, data=payload, headers=self.headers)
362 |
363 | return response.json()['ret']
364 | def exit(self,group_id,imgurl,nickName):
365 | import requests
366 | import json
367 | from datetime import datetime
368 | post_url = f"{self.base_url}/message/postAppMsg"
369 | now = datetime.now().strftime("%Y年%m月%d日 %H时%M分%S秒")
370 | payload = json.dumps({
371 | "appId": self.appid,
372 | "toWxid": group_id,
373 | "appmsg": (
374 | f''
375 | f' {nickName} 离开群聊!'
376 | f'⌚:{now}\n{self.say_exit}'
377 | f'view50'
378 | f'{self.exit_url}'
379 | f''
380 | f'{imgurl}'
381 | ''
382 | '0'
383 | ''
384 | '0'
385 | '0'
386 | ''
387 | )
388 | })
389 | response = requests.request("POST", post_url, data=payload, headers=self.headers)
390 |
391 | return response.json()['ret']
392 | def get_info(self,group_id,nickname):
393 | import requests
394 | import json
395 | print('----get_info----')
396 | wxid=self.get_list(group_id,nickname)
397 | if wxid==None:
398 | return None
399 | payload = json.dumps({
400 | "appId": self.appid,
401 | "chatroomId": group_id,
402 | "memberWxids": [
403 | wxid
404 | ]
405 | })
406 | data=requests.request("POST", f"{self.base_url}/group/getChatroomMemberDetail", data=payload, headers=self.headers).json()
407 | print('----get_info----',data["data"][0]["signature"],data["data"][0]["smallHeadImgUrl"],data["data"][0]["nickName"])
408 | return data["data"][0]["signature"],data["data"][0]["smallHeadImgUrl"],data["data"][0]["nickName"]
409 | def get_list(self,group_id,nickname):
410 | print('----get_list----')
411 | print('----group_id----',group_id,nickname)
412 | import requests
413 | import json
414 | payload = json.dumps({
415 | "appId": self.appid,
416 | "chatroomId": group_id,
417 | })
418 |
419 | data=requests.request("POST", f"{self.base_url}/group/getChatroomMemberList", data=payload, headers=self.headers).json()
420 | ret=data['ret']
421 | if ret!=200:
422 | return None
423 | wxid=None
424 |
425 | for member in data["data"]["memberList"]:
426 | if member["nickName"] == nickname:
427 | wxid=member["wxid"]
428 | print('----get_list----',wxid)
429 | return wxid
430 | def get_member_list(self, other_user_id, other_user_nickname):
431 | """
432 | 获取群成员列表并监控退群行为
433 | """
434 | print('----get_member_list----')
435 | import requests
436 | import json
437 | import time
438 | import threading
439 |
440 | # 清理已存在的监控线程
441 | if other_user_id in self.monitor_threads:
442 | # 从监控集合中移除
443 | if other_user_id in self.monitoring_groups:
444 | self.monitoring_groups.remove(other_user_id)
445 | if other_user_id in self.monitoring_groups_name:
446 | self.monitoring_groups_name.pop(other_user_id)
447 | # 等待旧线程结束
448 | if self.monitor_threads[other_user_id].is_alive():
449 | time.sleep(self.sleep_time + 1)
450 | # 清理线程记录
451 | self.monitor_threads.pop(other_user_id)
452 |
453 | def monitor_group(group_id):
454 | while group_id in self.monitoring_groups:
455 | try:
456 | payload = json.dumps({
457 | "appId": self.appid,
458 | "chatroomId": group_id,
459 | })
460 |
461 | data = requests.request("POST", f"{self.base_url}/group/getChatroomMemberList",
462 | data=payload, headers=self.headers).json()
463 |
464 | if data.get('ret') != 200:
465 | logger.error(f"[HelloPlus] Failed to get member list for group {group_id}: {data}")
466 | time.sleep(self.sleep_time)
467 | continue
468 |
469 | current_members = data["data"]["memberList"]
470 |
471 | if group_id not in self.group_members:
472 | self.group_members[group_id] = current_members
473 | else:
474 | old_members = self.group_members[group_id]
475 | old_wxids = {m["wxid"] for m in old_members}
476 | new_wxids = {m["wxid"] for m in current_members}
477 |
478 | left_members = old_wxids - new_wxids
479 | if left_members:
480 | for wxid in left_members:
481 | member = next(m for m in old_members if m["wxid"] == wxid)
482 | logger.info(f"[HelloPlus] User {member['nickName']} left group {group_id}")
483 | self.exit(group_id, member['smallHeadImgUrl'], member['nickName'])
484 |
485 | self.group_members[group_id] = current_members
486 |
487 | self.memberList = current_members
488 | time.sleep(self.sleep_time)
489 |
490 | except Exception as e:
491 | logger.error(f"[HelloPlus] Error monitoring group {group_id}: {e}")
492 | if group_id not in self.monitoring_groups:
493 | break
494 | time.sleep(self.sleep_time)
495 | continue
496 |
497 | logger.info(f"[HelloPlus] Stopped monitoring group {group_id}")
498 |
499 | # 启动新的监控线程
500 | self.monitoring_groups.add(other_user_id)
501 | self.monitoring_groups_name[other_user_id] = other_user_nickname
502 | t = threading.Thread(target=monitor_group, args=(other_user_id,))
503 | t.daemon = True
504 | t.start()
505 | self.monitor_threads[other_user_id] = t
506 |
507 | return self.memberList
508 | def is_admin(self,wxid):
509 | return wxid in self.admin_user
510 | def add_admin_user(self,token,wxid):
511 | if token==self.auth_token:
512 | print('--**验证成功')
513 | self.admin_user.append(wxid)
514 | print(self.admin_user)
515 | return True
516 | return False
517 | def create_reply(self, reply_type, content):
518 | reply = Reply()
519 | reply.type = reply_type
520 | reply.content = content
521 | return reply
522 | def get_group_list(self):
523 | import requests
524 | import json
525 | url = f"{self.base_url}/contacts/fetchContactsList"
526 |
527 | payload = json.dumps({
528 | "appId": self.appid
529 | })
530 |
531 | response = requests.request("POST", url, data=payload, headers=self.headers)
532 | response=response.json()
533 | if response['ret']!=200:
534 | return None
535 | rooms=response['data']['chatrooms']
536 | self.rooms=rooms
537 | print(rooms)
538 | self.get_group_info(rooms)
539 | def get_group_info(self,rooms):
540 | import requests
541 | import json
542 | import time
543 | url = f"{self.base_url}/contacts/getDetailInfo"
544 |
545 | payload = json.dumps({
546 | "appId": self.appid,
547 | "wxids": rooms
548 | })
549 |
550 | response = requests.request("POST", url, data=payload, headers=self.headers)
551 | response=response.json()
552 | if response['ret']!=200:
553 | return None
554 | datas=response['data']
555 |
556 | for group_name in self.group_names:
557 | found = False
558 | for data in datas:
559 | self.ql_list[data['userName']]=data['nickName']
560 | if data['nickName'] == group_name:
561 | time.sleep(1)
562 | self.get_member_list(data['userName'], data['nickName'])
563 | found = True
564 | break
565 | if not found:
566 | print(f"群组 {group_name} 未找到")
567 |
568 | return self.ql_list
569 | def start_monitor(self, group_name):
570 | try:
571 | # 遍历self.ql_list找到group_name对应的group_id
572 | for group_id, name in self.ql_list.items():
573 | if name == group_name:
574 | # 找到匹配的群组,调用get_member_list
575 | try:
576 | self.get_member_list(group_id, group_name)
577 | logger.info(f"监控启动成功: {group_name}")
578 | return True, f"监控启动成功: {group_name}"
579 | except Exception as e:
580 | error_msg = f"启动群监控失败: {group_name} "
581 | logger.error(f"启动群监控失败 {str(e)}")
582 | return False, error_msg
583 |
584 | return False, f"未找到群组: {group_name}"
585 | except Exception as e:
586 | error_msg = f"启动监控时发生错误: {group_name}"
587 | logger.error(f"[启动监控时发生错误] : {str(e)}")
588 | return False, error_msg
--------------------------------------------------------------------------------