这次实际改的是按钮背后的同步逻辑,不是页面文案本身。现在 /contacts/api/update 触发后会:
个人联系人、公众号:已存在就跳过,不存在才写入。 群:不再删库重建,已有群保留;新群会写入。 群成员:已存在就跳过,不存在就写入。 如果成员这次不在群里了,会把 db/contacts_db.py 里的 status 标成 2,前端会显示“已退群”。 如果整个群查不到了,也不再删除群资料,只把该群历史成员标记为“已退群”。 改动在: robot.py db/contacts_db.py 我还做了语法校验,py_compile 通过。需要的话我也可以继续帮你把“更新通讯录”按钮的成功提示改成更明确,比如“已完成增量同步,未删除历史数据”。
This commit is contained in:
@@ -113,12 +113,13 @@ class ContactsDBOperator(BaseDBOperator):
|
|||||||
self.LOG.error(f"创建微信联系人表或群成员表失败: {e}")
|
self.LOG.error(f"创建微信联系人表或群成员表失败: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def save_contacts(self, contacts_data: List[Dict], contact_type: str) -> bool:
|
def save_contacts(self, contacts_data: List[Dict], contact_type: str, only_insert: bool = False) -> bool:
|
||||||
"""保存联系人信息到数据库
|
"""保存联系人信息到数据库
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
contacts_data: 联系人数据列表
|
contacts_data: 联系人数据列表
|
||||||
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
|
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
|
||||||
|
only_insert: 为True时仅写入不存在的联系人,已存在则跳过
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 是否成功保存
|
bool: 是否成功保存
|
||||||
@@ -165,14 +166,18 @@ class ContactsDBOperator(BaseDBOperator):
|
|||||||
placeholders = ', '.join(['%s'] * len(data))
|
placeholders = ', '.join(['%s'] * len(data))
|
||||||
values = tuple(data.values())
|
values = tuple(data.values())
|
||||||
|
|
||||||
# 使用INSERT ... ON DUPLICATE KEY UPDATE语法
|
if only_insert:
|
||||||
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name'])
|
sql = f"""
|
||||||
|
INSERT IGNORE INTO t_wechat_contacts ({fields})
|
||||||
sql = f"""
|
VALUES ({placeholders})
|
||||||
INSERT INTO t_wechat_contacts ({fields})
|
"""
|
||||||
VALUES ({placeholders})
|
else:
|
||||||
ON DUPLICATE KEY UPDATE {update_clause}
|
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name'])
|
||||||
"""
|
sql = f"""
|
||||||
|
INSERT INTO t_wechat_contacts ({fields})
|
||||||
|
VALUES ({placeholders})
|
||||||
|
ON DUPLICATE KEY UPDATE {update_clause}
|
||||||
|
"""
|
||||||
|
|
||||||
self.execute_update(sql, values)
|
self.execute_update(sql, values)
|
||||||
|
|
||||||
@@ -315,7 +320,7 @@ class ContactsDBOperator(BaseDBOperator):
|
|||||||
self.LOG.error(f"获取所有联系人信息失败: {e}")
|
self.LOG.error(f"获取所有联系人信息失败: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def save_chatroom_member_simple(self, chatroom_id: str, member_details: List[Dict]) -> bool:
|
def save_chatroom_member_simple(self, chatroom_id: str, member_details: List[Dict], only_insert: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
保存群成员简要信息到数据库,兼容不同数据结构
|
保存群成员简要信息到数据库,兼容不同数据结构
|
||||||
Args:
|
Args:
|
||||||
@@ -367,13 +372,18 @@ class ContactsDBOperator(BaseDBOperator):
|
|||||||
placeholders = ', '.join(['%s'] * len(data))
|
placeholders = ', '.join(['%s'] * len(data))
|
||||||
values = tuple(data.values())
|
values = tuple(data.values())
|
||||||
|
|
||||||
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k not in ('chatroom_id', 'wxid')])
|
if only_insert:
|
||||||
|
sql = f"""
|
||||||
sql = f"""
|
INSERT IGNORE INTO t_chatroom_member ({fields})
|
||||||
INSERT INTO t_chatroom_member ({fields})
|
VALUES ({placeholders})
|
||||||
VALUES ({placeholders})
|
"""
|
||||||
ON DUPLICATE KEY UPDATE {update_clause}
|
else:
|
||||||
"""
|
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k not in ('chatroom_id', 'wxid')])
|
||||||
|
sql = f"""
|
||||||
|
INSERT INTO t_chatroom_member ({fields})
|
||||||
|
VALUES ({placeholders})
|
||||||
|
ON DUPLICATE KEY UPDATE {update_clause}
|
||||||
|
"""
|
||||||
|
|
||||||
self.execute_update(sql, values)
|
self.execute_update(sql, values)
|
||||||
|
|
||||||
@@ -567,6 +577,52 @@ class ContactsDBOperator(BaseDBOperator):
|
|||||||
self.LOG.error(f"更新群聊{chatroom_id}信息失败: {e}")
|
self.LOG.error(f"更新群聊{chatroom_id}信息失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def mark_chatroom_members_left(self, chatroom_id: str, active_member_wxids: Optional[List[str]] = None) -> bool:
|
||||||
|
"""将不在当前群成员列表中的用户标记为已退群。"""
|
||||||
|
try:
|
||||||
|
active_member_wxids = [wxid for wxid in (active_member_wxids or []) if wxid]
|
||||||
|
if active_member_wxids:
|
||||||
|
placeholders = ', '.join(['%s'] * len(active_member_wxids))
|
||||||
|
sql = f"""
|
||||||
|
UPDATE t_chatroom_member
|
||||||
|
SET status = 2
|
||||||
|
WHERE chatroom_id = %s AND wxid NOT IN ({placeholders})
|
||||||
|
"""
|
||||||
|
params = (chatroom_id, *active_member_wxids)
|
||||||
|
else:
|
||||||
|
sql = """
|
||||||
|
UPDATE t_chatroom_member
|
||||||
|
SET status = 2
|
||||||
|
WHERE chatroom_id = %s
|
||||||
|
"""
|
||||||
|
params = (chatroom_id,)
|
||||||
|
|
||||||
|
self.execute_update(sql, params)
|
||||||
|
self.LOG.info(f"已将群 {chatroom_id} 中缺失成员标记为已退群")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.LOG.error(f"标记群{chatroom_id}成员退群失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mark_chatroom_members_active(self, chatroom_id: str, active_member_wxids: List[str]) -> bool:
|
||||||
|
"""将当前仍在群里的成员标记为在群。"""
|
||||||
|
try:
|
||||||
|
active_member_wxids = [wxid for wxid in (active_member_wxids or []) if wxid]
|
||||||
|
if not active_member_wxids:
|
||||||
|
return True
|
||||||
|
|
||||||
|
placeholders = ', '.join(['%s'] * len(active_member_wxids))
|
||||||
|
sql = f"""
|
||||||
|
UPDATE t_chatroom_member
|
||||||
|
SET status = 1
|
||||||
|
WHERE chatroom_id = %s AND wxid IN ({placeholders})
|
||||||
|
"""
|
||||||
|
self.execute_update(sql, (chatroom_id, *active_member_wxids))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.LOG.error(f"恢复群{chatroom_id}成员在群状态失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def delete_chatroom_info(self, chatroom_id: str) -> bool:
|
def delete_chatroom_info(self, chatroom_id: str) -> bool:
|
||||||
"""删除群信息"""
|
"""删除群信息"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
89
robot.py
89
robot.py
@@ -630,66 +630,83 @@ class Robot:
|
|||||||
|
|
||||||
async def refresh_contacts_db(self):
|
async def refresh_contacts_db(self):
|
||||||
"""刷新联系人信息"""
|
"""刷新联系人信息"""
|
||||||
# 获取用户所有的联系人,并保存到数据库
|
|
||||||
self.LOG.info("开始刷新联系人信息")
|
self.LOG.info("开始刷新联系人信息")
|
||||||
# 删除所有的联系人信息
|
|
||||||
self.contacts_db.delete_all_contacts()
|
|
||||||
self.LOG.debug("已删除所有的联系人信息")
|
|
||||||
contacts = await self.ipad_bot.get_contract_list()
|
contacts = await self.ipad_bot.get_contract_list()
|
||||||
self.LOG.debug(f"获取到的联系人:{contacts}")
|
self.LOG.debug(f"获取到的联系人:{contacts}")
|
||||||
# 获取联系人详细信息,get_contract_detail每次可以获取20个
|
|
||||||
# 每次获取20个,需要循环获取
|
|
||||||
# 将联系人列表分成每组20个
|
|
||||||
batch_size = 20
|
batch_size = 20
|
||||||
|
discovered_groups = set()
|
||||||
for i in range(0, len(contacts), batch_size):
|
for i in range(0, len(contacts), batch_size):
|
||||||
# 获取当前批次的联系人
|
|
||||||
batch_contacts = contacts[i:i + batch_size]
|
batch_contacts = contacts[i:i + batch_size]
|
||||||
# 获取这批联系人的详细信息
|
|
||||||
contact_info = await self.ipad_bot.get_contract_detail(batch_contacts)
|
contact_info = await self.ipad_bot.get_contract_detail(batch_contacts)
|
||||||
self.LOG.debug(f"获取到的联系人详细信息数量:{len(contact_info)}")
|
self.LOG.debug(f"获取到的联系人详细信息数量:{len(contact_info)}")
|
||||||
self.contacts_db.save_contacts(contact_info, "friends")
|
friend_contacts = []
|
||||||
|
official_contacts = []
|
||||||
|
|
||||||
|
for contact in contact_info:
|
||||||
|
user_name = contact.get("UserName")
|
||||||
|
if isinstance(user_name, dict):
|
||||||
|
user_name = user_name.get("string", "")
|
||||||
|
user_name = user_name or ""
|
||||||
|
|
||||||
|
if not user_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_name.endswith("@chatroom"):
|
||||||
|
discovered_groups.add(user_name)
|
||||||
|
if not self.contacts_db.get_chatroom_info(user_name):
|
||||||
|
self.contacts_db.save_chatroom_info(contact)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_name.startswith("gh_"):
|
||||||
|
official_contacts.append(contact)
|
||||||
|
else:
|
||||||
|
friend_contacts.append(contact)
|
||||||
|
|
||||||
|
if friend_contacts:
|
||||||
|
self.contacts_db.save_contacts(friend_contacts, "friends", only_insert=True)
|
||||||
|
if official_contacts:
|
||||||
|
self.contacts_db.save_contacts(official_contacts, "ghs", only_insert=True)
|
||||||
|
|
||||||
# 获取群聊列表
|
|
||||||
groups = self.contacts_db.get_chatroom_list()
|
groups = self.contacts_db.get_chatroom_list()
|
||||||
# 调用接口完成群成员信息获取与保存逻辑
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
# 调用接口获取群成员信息
|
|
||||||
group_id = group["chatroom_id"]
|
group_id = group["chatroom_id"]
|
||||||
|
discovered_groups.add(group_id)
|
||||||
chatroom_info = await self.ipad_bot.get_chatroom_info(group_id)
|
chatroom_info = await self.ipad_bot.get_chatroom_info(group_id)
|
||||||
self.LOG.debug(f"获取到的群成员信息:{chatroom_info}")
|
self.LOG.debug(f"获取到的群成员信息:{chatroom_info}")
|
||||||
if chatroom_info.get("UserName", ""):
|
if chatroom_info.get("UserName", ""):
|
||||||
# 保存群信息到数据库
|
|
||||||
self.contacts_db.save_chatroom_info(chatroom_info)
|
|
||||||
members = await self.ipad_bot.get_chatroom_member_list(group_id)
|
members = await self.ipad_bot.get_chatroom_member_list(group_id)
|
||||||
# 保存群成员信息
|
|
||||||
if members:
|
if members:
|
||||||
# 清除所有的成员信息, 重新写入。
|
active_member_wxids = []
|
||||||
self.contacts_db.delete_chatroom_members_info(group_id)
|
|
||||||
# 兼容逻辑已放到save_chatroom_member_simple内部
|
|
||||||
self.contacts_db.save_chatroom_member_simple(group_id, members)
|
|
||||||
self.LOG.info(f"member_list: {members}")
|
|
||||||
# 更新联系人缓存
|
|
||||||
for member in members:
|
for member in members:
|
||||||
wxid = member.get("UserName", "")
|
wxid = member.get("UserName", "")
|
||||||
nick_name = member.get("NickName", "")
|
if isinstance(wxid, dict):
|
||||||
displayName = member.get("DisplayName", "")
|
wxid = wxid.get("string", "")
|
||||||
small_head_img_url = member.get("SmallHeadImgUrl", "")
|
|
||||||
# 如果displayName不为空,使用displayName
|
|
||||||
if displayName:
|
|
||||||
nick_name = displayName
|
|
||||||
if wxid:
|
if wxid:
|
||||||
self.allContacts[wxid] = nick_name
|
active_member_wxids.append(wxid)
|
||||||
|
|
||||||
self.head_images[wxid] = small_head_img_url
|
self.contacts_db.mark_chatroom_members_active(group_id, active_member_wxids)
|
||||||
|
self.contacts_db.mark_chatroom_members_left(group_id, active_member_wxids)
|
||||||
self.LOG.info(f"已更新群 {group_id} 的成员信息")
|
self.contacts_db.save_chatroom_member_simple(group_id, members, only_insert=True)
|
||||||
|
self.LOG.info(f"已增量同步群 {group_id} 的成员信息")
|
||||||
|
else:
|
||||||
|
self.contacts_db.mark_chatroom_members_left(group_id, [])
|
||||||
|
self.LOG.warning(f"群 {group_id} 当前未获取到成员列表,已将历史成员标记为已退群")
|
||||||
else:
|
else:
|
||||||
self.LOG.error(f"获取群 {group_id} 信息失败,证明用户无该群信息,删除群的相关资料。")
|
self.LOG.warning(f"获取群 {group_id} 信息失败,保留群资料并将成员标记为已退群。")
|
||||||
# 删除群数据库中的群信息
|
self.contacts_db.mark_chatroom_members_left(group_id, [])
|
||||||
self.contacts_db.delete_chatroom_all_info(group_id)
|
|
||||||
self.allContacts.pop(group_id)
|
for group_id in discovered_groups:
|
||||||
|
if not self.contacts_db.get_chatroom_info(group_id):
|
||||||
|
chatroom_info = await self.ipad_bot.get_chatroom_info(group_id)
|
||||||
|
if chatroom_info.get("UserName", ""):
|
||||||
|
self.contacts_db.save_chatroom_info(chatroom_info)
|
||||||
|
members = await self.ipad_bot.get_chatroom_member_list(group_id)
|
||||||
|
if members:
|
||||||
|
self.contacts_db.save_chatroom_member_simple(group_id, members, only_insert=True)
|
||||||
|
|
||||||
friends = await self.ipad_bot.get_contract_list()
|
friends = await self.ipad_bot.get_contract_list()
|
||||||
|
self.allContacts = self.get_all_contacts()
|
||||||
|
self.head_images = self.get_all_head_images()
|
||||||
self.all_chatroom_members = self.contacts_db.get_chatroom_member_list_name_all()
|
self.all_chatroom_members = self.contacts_db.get_chatroom_member_list_name_all()
|
||||||
self.contact_manager.set_contacts(self.allContacts, friends, self.head_images,
|
self.contact_manager.set_contacts(self.allContacts, friends, self.head_images,
|
||||||
self.all_chatroom_members)
|
self.all_chatroom_members)
|
||||||
|
|||||||
Reference in New Issue
Block a user