diff --git a/admin/dashboard/server.py b/admin/dashboard/server.py index ed636b4..de59e56 100644 --- a/admin/dashboard/server.py +++ b/admin/dashboard/server.py @@ -247,24 +247,95 @@ class DashboardServer: def api_contacts_statistics(): """获取联系人统计信息API""" try: - contacts = self.contact_manager.get_contacts() - group_contacts = {wxid: name for wxid, name in contacts.items() - if '@@' in wxid or '@chatroom' in wxid} - personal_contacts = {wxid: name for wxid, name in contacts.items() - if '@@' not in wxid and '@chatroom' not in wxid} + # 使用新的联系人分类方法获取统计信息 + total, groups, personal, public, official = self.contact_manager.get_contact_statistics() return jsonify({ "success": True, "data": { - "total": len(contacts), - "groups": len(group_contacts), - "personal": len(personal_contacts) + "total": total, + "groups": groups, + "personal": personal, + "public": public, + "official": official } }) except Exception as e: self.logger.error(f"获取联系人统计信息失败: {e}") return jsonify({"success": False, "error": str(e)}), 500 + # 修改群组联系人API,使用新的分类方法 + @app.route('/api/contacts/groups', methods=['GET']) + @login_required + def api_contacts_groups(): + """获取群组联系人信息API""" + try: + group_contacts = self.contact_manager.get_group_contacts() + + return jsonify({ + "success": True, + "data": { + "groups": group_contacts + } + }) + except Exception as e: + self.logger.error(f"获取群组联系人信息失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + # 修改个人联系人API,使用新的分类方法 + @app.route('/api/contacts/personal', methods=['GET']) + @login_required + def api_contacts_personal(): + """获取个人联系人信息API""" + try: + personal_contacts = self.contact_manager.get_personal_contacts() + + return jsonify({ + "success": True, + "data": { + "personal": personal_contacts + } + }) + except Exception as e: + self.logger.error(f"获取个人联系人信息失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + # 添加公众号联系人API + @app.route('/api/contacts/official', methods=['GET']) + @login_required + def api_contacts_official(): + """获取公众号联系人信息API""" + try: + official_accounts = self.contact_manager.get_official_accounts() + + return jsonify({ + "success": True, + "data": { + "official": official_accounts + } + }) + except Exception as e: + self.logger.error(f"获取公众号联系人信息失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + # 添加公共好友API + @app.route('/api/contacts/public', methods=['GET']) + @login_required + def api_contacts_public(): + """获取公共好友信息API""" + try: + public_contacts = self.contact_manager.get_public_contacts() + + return jsonify({ + "success": True, + "data": { + "public": public_contacts + } + }) + except Exception as e: + self.logger.error(f"获取公共好友信息失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + @app.route('/api/robot/groups') @login_required def api_robot_groups(): diff --git a/admin/dashboard/templates/contacts_management.html b/admin/dashboard/templates/contacts_management.html index 34cca41..7b7cd95 100644 --- a/admin/dashboard/templates/contacts_management.html +++ b/admin/dashboard/templates/contacts_management.html @@ -27,24 +27,30 @@ - +
总联系人数
{% raw %}{{ statistics.total }}{% endraw %}
- +
群组数
{% raw %}{{ statistics.groups }}{% endraw %}
- +
个人联系人数
{% raw %}{{ statistics.personal }}{% endraw %}
+ + +
公众号数
+
{% raw %}{{ statistics.official }}{% endraw %}
+
+
@@ -92,6 +98,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -118,6 +168,30 @@ {% raw %}:total="personalList.length"{% endraw %}> + +
+ + +
+ +
+ + +
@@ -139,6 +213,24 @@ + + + + + {% raw %}{{ currentOfficial.wxid }}{% endraw %} + {% raw %}{{ currentOfficial.name }}{% endraw %} + + + + + + + + {% raw %}{{ currentPublic.wxid }}{% endraw %} + {% raw %}{{ currentPublic.name }}{% endraw %} + + + {% endblock %} @@ -155,15 +247,23 @@ pageSize: 10, groupsList: [], personalList: [], + officialList: [], + publicList: [], statistics: { total: 0, groups: 0, - personal: 0 + personal: 0, + official: 0, + public: 0 }, groupDetailDialogVisible: false, userDetailDialogVisible: false, + officialDetailDialogVisible: false, + publicDetailDialogVisible: false, currentGroup: {}, - currentUser: {} + currentUser: {}, + currentOfficial: {}, + currentPublic: {} }; }, computed: { @@ -200,6 +300,40 @@ (this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize ); + }, + filteredOfficial() { + const query = this.searchQuery.toLowerCase(); + if (!query) { + return this.officialList.slice( + (this.currentPage - 1) * this.pageSize, + this.currentPage * this.pageSize + ); + } + + return this.officialList.filter(official => + official.wxid.toLowerCase().includes(query) || + official.name.toLowerCase().includes(query) + ).slice( + (this.currentPage - 1) * this.pageSize, + this.currentPage * this.pageSize + ); + }, + filteredPublic() { + const query = this.searchQuery.toLowerCase(); + if (!query) { + return this.publicList.slice( + (this.currentPage - 1) * this.pageSize, + this.currentPage * this.pageSize + ); + } + + return this.publicList.filter(item => + item.wxid.toLowerCase().includes(query) || + item.name.toLowerCase().includes(query) + ).slice( + (this.currentPage - 1) * this.pageSize, + this.currentPage * this.pageSize + ); } }, mounted() { @@ -251,6 +385,38 @@ console.error('加载个人联系人数据失败:', error); this.$message.error('加载个人联系人数据失败'); }); + + // 加载公众号数据 + axios.get('/api/contacts/official') + .then(response => { + if (response.data.success) { + const official = response.data.data.official; + this.officialList = Object.entries(official).map(([wxid, name]) => ({ + wxid, + name + })); + } + }) + .catch(error => { + console.error('加载公众号数据失败:', error); + this.$message.error('加载公众号数据失败'); + }); + + // 加载公共好友数据 + axios.get('/api/contacts/public') + .then(response => { + if (response.data.success) { + const publicFriends = response.data.data.public; + this.publicList = Object.entries(publicFriends).map(([wxid, name]) => ({ + wxid, + name + })); + } + }) + .catch(error => { + console.error('加载公共好友数据失败:', error); + this.$message.error('加载公共好友数据失败'); + }); }, refreshContacts() { this.loadContactsData(); @@ -272,6 +438,14 @@ viewUserDetails(user) { this.currentUser = user; this.userDetailDialogVisible = true; + }, + viewOfficialDetails(official) { + this.currentOfficial = official; + this.officialDetailDialogVisible = true; + }, + viewPublicDetails(publicFriend) { + this.currentPublic = publicFriend; + this.publicDetailDialogVisible = true; } } }); diff --git a/utils/wechat/contact_manager.py b/utils/wechat/contact_manager.py index fda1dee..bcfba39 100644 --- a/utils/wechat/contact_manager.py +++ b/utils/wechat/contact_manager.py @@ -12,8 +12,19 @@ class ContactManager: _contacts: Dict[str, str] = {} _group_contacts: Dict[str, str] = {} # 群组联系人 _personal_contacts: Dict[str, str] = {} # 个人联系人 + _public_contacts: Dict[str, str] = {} # 公共好友 + _official_accounts: Dict[str, str] = {} # 公众号 _initialized = False _logger = logging.getLogger("ContactManager") + + # 定义公共好友列表 + _PUBLIC_FRIENDS = { + 'fmessage': '朋友推荐消息', + 'medianote': '语音记事本', + 'floatbottle': '漂流瓶', + 'mphelper': '公众平台安全助手', + 'filehelper': '文件传输助手' + } def __new__(cls): if cls._instance is None: @@ -40,24 +51,35 @@ class ContactManager: contacts: 联系人字典,格式为 {"wxid": "NickName"} """ self._contacts = contacts - self._logger.info(f"联系人信息:contacts={contacts}") self._logger.info(f"联系人信息已更新,共 {len(contacts)} 个联系人") # 分类联系人 self._classify_contacts() def _classify_contacts(self) -> None: - """将联系人分类为群组和个人联系人""" + """将联系人分类为群组、个人联系人、公共好友和公众号""" self._group_contacts = {} self._personal_contacts = {} + self._public_contacts = {} + self._official_accounts = {} for wxid, nickname in self._contacts.items(): - # 微信群ID通常以@@开头 - if wxid.startswith('@@'): + # 判断是否为公共好友 + if wxid in self._PUBLIC_FRIENDS: + self._public_contacts[wxid] = self._PUBLIC_FRIENDS.get(wxid, nickname) + # 判断是否为公众号(wxid以gh_开头) + elif wxid.startswith('gh_'): + self._official_accounts[wxid] = nickname + # 判断是否为群组(wxid以@chatroom结尾) + elif wxid.endswith('@chatroom'): self._group_contacts[wxid] = nickname + # 其他为普通好友和群成员 else: self._personal_contacts[wxid] = nickname - self._logger.info(f"联系人分类完成: {len(self._group_contacts)} 个群组, {len(self._personal_contacts)} 个个人联系人") + self._logger.info(f"联系人分类完成: {len(self._group_contacts)} 个群组, " + f"{len(self._personal_contacts)} 个个人联系人, " + f"{len(self._public_contacts)} 个公共好友, " + f"{len(self._official_accounts)} 个公众号") def get_contacts(self) -> Dict[str, str]: """获取所有联系人 @@ -82,6 +104,22 @@ class ContactManager: 个人联系人字典,格式为 {"wxid": "NickName"} """ return self._personal_contacts + + def get_public_contacts(self) -> Dict[str, str]: + """获取所有公共好友 + + Returns: + 公共好友字典,格式为 {"wxid": "NickName"} + """ + return self._public_contacts + + def get_official_accounts(self) -> Dict[str, str]: + """获取所有公众号 + + Returns: + 公众号字典,格式为 {"wxid": "NickName"} + """ + return self._official_accounts def get_nickname(self, wxid: str) -> str: """根据微信ID获取昵称 @@ -103,7 +141,11 @@ class ContactManager: """ self._contacts[wxid] = nickname # 更新分类 - if wxid.endswith('@chatroom'): + if wxid in self._PUBLIC_FRIENDS: + self._public_contacts[wxid] = self._PUBLIC_FRIENDS.get(wxid, nickname) + elif wxid.startswith('gh_'): + self._official_accounts[wxid] = nickname + elif wxid.endswith('@chatroom'): self._group_contacts[wxid] = nickname else: self._personal_contacts[wxid] = nickname @@ -120,10 +162,12 @@ class ContactManager: # 重新分类联系人 self._classify_contacts() - def get_contact_statistics(self) -> Tuple[int, int, int]: + def get_contact_statistics(self) -> Tuple[int, int, int, int, int]: """获取联系人统计信息 Returns: - 包含总联系人数、群组数和个人联系人数的元组 + 包含总联系人数、群组数、个人联系人数、公共好友数和公众号数的元组 """ - return len(self._contacts), len(self._group_contacts), len(self._personal_contacts) \ No newline at end of file + return (len(self._contacts), len(self._group_contacts), + len(self._personal_contacts), len(self._public_contacts), + len(self._official_accounts)) \ No newline at end of file