diff --git a/db/contacts_db.py b/db/contacts_db.py index 5e57aca..e8d5387 100644 --- a/db/contacts_db.py +++ b/db/contacts_db.py @@ -712,3 +712,30 @@ class ContactsDBOperator(BaseDBOperator): except Exception as e: self.LOG.error(f"更新群{chatroom_id}成员{wxid}活跃时间失败: {e}") return False + + def get_inactive_members_rank(self, chatroom_id: str, days: int = 60, limit: int = 10) -> List[Dict]: + try: + sql = """ + SELECT + wxid, + COALESCE(NULLIF(display_name,''), nick_name, wxid) AS nick_name, + latest_active_time, + CASE + WHEN latest_active_time IS NULL THEN 999999 + ELSE TIMESTAMPDIFF(DAY, latest_active_time, NOW()) + END AS inactivity_days + FROM t_chatroom_member + WHERE chatroom_id = %s + AND (latest_active_time IS NULL OR latest_active_time <= DATE_SUB(NOW(), INTERVAL %s DAY)) + ORDER BY inactivity_days DESC + LIMIT %s + """ + results = self.execute_query(sql, (chatroom_id, days, limit)) + for row in results: + dt = row.get("latest_active_time") + if isinstance(dt, datetime): + row["latest_active_time"] = dt.strftime("%Y-%m-%d %H:%M:%S") + return results + except Exception as e: + self.LOG.error(f"获取群{chatroom_id}潜水排行失败: {e}") + return [] diff --git a/plugins/inactive_rank/__init__.py b/plugins/inactive_rank/__init__.py new file mode 100644 index 0000000..958fbc2 --- /dev/null +++ b/plugins/inactive_rank/__init__.py @@ -0,0 +1,4 @@ +from .main import InactiveRankPlugin + +def get_plugin(): + return InactiveRankPlugin() diff --git a/plugins/inactive_rank/config.toml b/plugins/inactive_rank/config.toml new file mode 100644 index 0000000..7ef6e58 --- /dev/null +++ b/plugins/inactive_rank/config.toml @@ -0,0 +1,11 @@ +[InactiveRank] +enable = true +command = ["潜水排行"] +command-format = """ +📉 潜水排行用法: +潜水排行 [天数] [名次] +默认:60天内不活跃,取前10名 +示例:潜水排行 30 10 +""" +default_days = 60 +default_limit = 10 diff --git a/plugins/inactive_rank/main.py b/plugins/inactive_rank/main.py new file mode 100644 index 0000000..2f92cbc --- /dev/null +++ b/plugins/inactive_rank/main.py @@ -0,0 +1,123 @@ +from typing import Dict, Any, List, Optional, Tuple +from loguru import logger + +from base.plugin_common.message_plugin_interface import MessagePluginInterface +from base.plugin_common.plugin_interface import PluginStatus +from utils.decorator.plugin_decorators import plugin_stats_decorator +from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager +from db.connection import DBConnectionManager +from db.contacts_db import ContactsDBOperator +from wechat_ipad import WechatAPIClient + + +class InactiveRankPlugin(MessagePluginInterface): + FEATURE_KEY = "INACTIVE_RANK" + FEATURE_DESCRIPTION = "📉 潜水排行 [潜水排行]" + + @property + def name(self) -> str: + return "潜水排行" + + @property + def version(self) -> str: + return "1.0.0" + + @property + def description(self) -> str: + return "统计群内在指定天数内未活跃的成员排行" + + @property + def author(self) -> str: + return "liu.wei" + + @property + def command_prefix(self) -> Optional[str]: + return "" + + @property + def commands(self) -> List[str]: + return self._commands + + @property + def feature_key(self) -> Optional[str]: + return self.FEATURE_KEY + + @property + def feature_description(self) -> Optional[str]: + return self.FEATURE_DESCRIPTION + + def __init__(self): + super().__init__() + self.feature = self.register_feature() + + def initialize(self, context: Dict[str, Any]) -> bool: + self.LOG = logger + self._commands = self._config.get("InactiveRank", {}).get("command", ["潜水排行"]) + self.command_format = self._config.get("InactiveRank", {}).get("command-format", "潜水排行 [天数] [名次]") + self.enable = self._config.get("InactiveRank", {}).get("enable", True) + self.default_days = int(self._config.get("InactiveRank", {}).get("default_days", 60)) + self.default_limit = int(self._config.get("InactiveRank", {}).get("default_limit", 10)) + return True + + def start(self) -> bool: + self.status = PluginStatus.RUNNING + return True + + def stop(self) -> bool: + self.status = PluginStatus.STOPPED + return True + + def can_process(self, message: Dict[str, Any]) -> bool: + if not self.enable: + return False + content = str(message.get("content", "")).strip() + command = content.split(" ")[0] + return command in self._commands + + @plugin_stats_decorator(plugin_name="潜水排行") + async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]: + content = str(message.get("content", "")).strip() + command = content.split(" ")[0] + sender = message.get("sender") + roomid = message.get("roomid", "") + gbm: GroupBotManager = message.get("gbm") + bot: WechatAPIClient = message.get("bot") + + if roomid and gbm.get_group_permission(roomid, self.feature) == PermissionStatus.DISABLED: + return False, "没有权限" + + if not roomid: + await bot.send_text_message((sender), "请在群聊中使用该功能", sender) + return False, "非群聊" + + parts = content.split() + days = self.default_days + limit = self.default_limit + if len(parts) >= 2: + try: + days = int(parts[1]) + except Exception: + await bot.send_text_message(roomid, f"❌命令格式错误!\n{self.command_format}", sender) + return False, "命令格式错误" + if len(parts) >= 3: + try: + limit = int(parts[2]) + except Exception: + await bot.send_text_message(roomid, f"❌命令格式错误!\n{self.command_format}", sender) + return False, "命令格式错误" + + db = ContactsDBOperator(DBConnectionManager.get_instance()) + rows = db.get_inactive_members_rank(roomid, days, limit) + if not rows: + await bot.send_text_message(roomid, f"📉 {days}天内暂无潜水成员", sender) + return True, "暂无数据" + + lines = [f"📉 {days}天潜水排行,取前{limit}名"] + for i, r in enumerate(rows, 1): + name = r.get("nick_name") or r.get("wxid") + inactivity = int(r.get("inactivity_days", 0)) + last = r.get("latest_active_time") or "未知" + lines.append(f"{i}. {name} | 潜水{inactivity}天 | 上次活跃: {last}") + msg = "\n".join(lines) + await bot.send_text_message(roomid, msg, sender) + return True, "已发送"