diff --git a/plugins/xiuxian/main.py b/plugins/xiuxian/main.py index 5e8f032..48359cf 100644 --- a/plugins/xiuxian/main.py +++ b/plugins/xiuxian/main.py @@ -103,21 +103,63 @@ class XiuxianRedisDB: except Exception as e: logger.error(f"失效玩家缓存失败: {e}") - def set_rate_limit(self, user_id: str, cmd: str, seconds: int) -> bool: + def set_rate_limit(self, user_id: str, group_id: str, cmd: str, seconds: int) -> bool: + """设置限流,支持自然日重置(当seconds >= 86400时)""" try: with self.get_redis() as r: - key = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}" - r.setex(key, seconds, "1") + # 如果是24小时或更长的限流(如签到),使用基于日期的键 + if seconds >= 86400: + # 使用自然日:键包含日期,过期时间设为到明天0点的秒数 + now = datetime.now(timezone.utc) + # 获取明天的0点(UTC) + tomorrow = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) + # 计算到明天0点的秒数 + expire_seconds = int((tomorrow - now).total_seconds()) + # 使用日期作为键的一部分,包含group_id + date_str = now.strftime("%Y-%m-%d") + key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}:date:{date_str}" + r.setex(key, expire_seconds, "1") + else: + # 短时间限流,使用原来的逻辑,包含group_id + key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}" + r.setex(key, seconds, "1") return True except Exception as e: logger.error(f"设置限流失败: {e}") return False - def check_rate_limited(self, user_id: str, cmd: str) -> bool: + def check_rate_limited(self, user_id: str, group_id: str, cmd: str) -> bool: + """检查限流,支持自然日重置(当限流时间 >= 86400秒时)""" try: with self.get_redis() as r: - key = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}" - return r.exists(key) == 1 + # 检查是否是长时间限流(如签到) + # 先尝试基于日期的键(新格式,包含group_id) + now = datetime.now(timezone.utc) + date_str = now.strftime("%Y-%m-%d") + date_key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}:date:{date_str}" + + # 如果基于日期的键存在,说明今天已限流 + if r.exists(date_key) == 1: + return True + + # 兼容旧格式:检查是否有旧的限流键(不带group_id和日期) + old_key_no_group = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}" + old_key_with_date = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}:date:{date_str}" + + # 检查旧格式(不带group_id,带日期) + if r.exists(old_key_with_date) == 1: + return True + + # 检查旧格式(不带group_id,不带日期) + if r.exists(old_key_no_group) == 1: + # 如果旧键存在,检查是否过期(TTL > 0表示未过期) + ttl = r.ttl(old_key_no_group) + if ttl > 0: + return True + # 如果已过期,删除旧键 + r.delete(old_key_no_group) + + return False except Exception as e: logger.error(f"检查限流失败: {e}") return False @@ -424,7 +466,7 @@ class XiuxianPlugin(MessagePluginInterface): self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) return False, "Redis未初始化" - if self.redis_db.check_rate_limited(sender, cmd): + if self.redis_db.check_rate_limited(sender, roomid or "", cmd): client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "⚠️ 操作过于频繁,请稍候再试", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) @@ -473,10 +515,10 @@ class XiuxianPlugin(MessagePluginInterface): return False, "未知命令" - def _rate_set(self, user_id: str, cmd: str): + def _rate_set(self, user_id: str, group_id: str, cmd: str): """设置用户维度 Redis 冷却键,用于防骚扰与防封。""" seconds = self.seconds_rl.get(cmd, 3) - self.redis_db.set_rate_limit(user_id, cmd, seconds) + self.redis_db.set_rate_limit(user_id, group_id, cmd, seconds) def _check_status_update(self, player: Dict[str, Any]) -> Dict[str, Any]: """状态机自动流转:过期的 Unstable_Qi/Injured 恢复为 Idle。""" @@ -531,7 +573,7 @@ class XiuxianPlugin(MessagePluginInterface): except Exception as e: logger.warning(f"创建玩家数据库记录失败: {e}, user_id={sender}") self._save_player(player) - self._rate_set(sender, "注册修仙") + self._rate_set(sender, roomid or "", "注册修仙") # 初始化境界排行榜分值 self.redis_db.leaderboard_realm_add(sender, float(self._realm_score(player["realm"])) ) client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 注册成功,道号:{dao_name}\n灵根:{root_name}", sender) @@ -603,7 +645,7 @@ class XiuxianPlugin(MessagePluginInterface): stones_gain = pts // rate player["spirit_stone"] = int(player.get("spirit_stone", 0)) + stones_gain self._save_player(player) - self._rate_set(sender, "积分购石") + self._rate_set(sender, roomid or "", "积分购石") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 兑换成功,消耗积分{pts},获得灵石{stones_gain}({rate}积分=1灵石)", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -633,7 +675,7 @@ class XiuxianPlugin(MessagePluginInterface): client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), msg, sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20) - self._rate_set(sender, "我的状态") + self._rate_set(sender, roomid or "", "我的状态") return True, "状态展示" async def _cmd_cultivate(self, bot: WechatAPIClient, sender: str, roomid: str) -> Tuple[bool, str]: @@ -664,7 +706,7 @@ class XiuxianPlugin(MessagePluginInterface): player["status"] = "Cultivating" player["last_cultivate_time"] = datetime.now(timezone.utc).isoformat() self._save_player(player) - self._rate_set(sender, "闭关") + self._rate_set(sender, roomid or "", "闭关") client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "✅ 已进入闭关,期间安全不可被劫掠", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -704,7 +746,7 @@ class XiuxianPlugin(MessagePluginInterface): # 自动层级提升(不跨瓶颈) self._auto_layer_up(sender, player) self.redis_db.leaderboard_add(sender, float(player["cultivation_points"])) - self._rate_set(sender, "出关") + self._rate_set(sender, roomid or "", "出关") client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 出关成功,获得修为:{gain}({duration_hours:.1f}小时)\n当前修为:{player['cultivation_points']}\n状态:气息不稳 {self.unstable_qi_minutes}分钟", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20) @@ -737,7 +779,7 @@ class XiuxianPlugin(MessagePluginInterface): # 自动层级提升(不跨瓶颈) self._auto_layer_up(sender, player) self.redis_db.leaderboard_add(sender, float(player["cultivation_points"])) - self._rate_set(sender, "聚灵") + self._rate_set(sender, roomid or "", "聚灵") client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 聚灵成功,消耗灵石{qty},获得修为{qty * 10}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -754,7 +796,7 @@ class XiuxianPlugin(MessagePluginInterface): client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "\n".join(lines), sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20) - self._rate_set(sender, "排行榜") + self._rate_set(sender, roomid or "", "排行榜") return True, "排行榜" def _get_player(self, user_id: str, group_id: str) -> Optional[Dict[str, Any]]: @@ -867,7 +909,7 @@ class XiuxianPlugin(MessagePluginInterface): # self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) return False, "未注册" player = self._check_status_update(player) - if self.redis_db.check_rate_limited(sender, "签到"): + if self.redis_db.check_rate_limited(sender, roomid or "", "签到"): # client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "今日已签到,请明日再来", sender) # if self.revoke: # self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) @@ -875,7 +917,7 @@ class XiuxianPlugin(MessagePluginInterface): reward = 50 player["spirit_stone"] = int(player.get("spirit_stone", 0)) + reward self._save_player(player) - self._rate_set(sender, "签到") + self._rate_set(sender, roomid or "", "签到") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 签到成功,获得灵石{reward}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -888,7 +930,7 @@ class XiuxianPlugin(MessagePluginInterface): client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "\n".join(lines), sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20) - self._rate_set(sender, "坊市") + self._rate_set(sender, roomid or "", "坊市") return True, "坊市" async def _cmd_buy(self, bot: WechatAPIClient, sender: str, roomid: str, content: str) -> Tuple[bool, str]: @@ -937,7 +979,7 @@ class XiuxianPlugin(MessagePluginInterface): except Exception: pass self._save_player(player) - self._rate_set(sender, "购买") + self._rate_set(sender, roomid or "", "购买") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 购买成功,{item_name} × {qty}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -966,7 +1008,7 @@ class XiuxianPlugin(MessagePluginInterface): client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "\n".join(lines), sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20) - self._rate_set(sender, "背包") + self._rate_set(sender, roomid or "", "背包") return True, "背包" async def _cmd_breakthrough(self, bot: WechatAPIClient, sender: str, roomid: str) -> Tuple[bool, str]: @@ -1055,7 +1097,7 @@ class XiuxianPlugin(MessagePluginInterface): # 成功,更新境界并排行榜 self._set_realm(sender, player, pill_conf["target"]) self.redis_db.leaderboard_add(sender, float(player["cultivation_points"])) - self._rate_set(sender, "突破") + self._rate_set(sender, roomid or "", "突破") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 突破成功,晋升至{pill_conf['target']}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1063,7 +1105,7 @@ class XiuxianPlugin(MessagePluginInterface): else: # 失败时也要保存玩家数据 self._save_player(player) - self._rate_set(sender, "突破") + self._rate_set(sender, roomid or "", "突破") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 突破失败", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) @@ -1116,7 +1158,7 @@ class XiuxianPlugin(MessagePluginInterface): if roll < hard_conf["rate"]: self._set_realm(sender, player, hard_conf["target"]) self.redis_db.leaderboard_add(sender, float(player["cultivation_points"])) - self._rate_set(sender, "强行突破") + self._rate_set(sender, roomid or "", "强行突破") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 强行突破成功,晋升至{hard_conf['target']}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1124,7 +1166,7 @@ class XiuxianPlugin(MessagePluginInterface): else: # 失败时也要保存玩家数据 self._save_player(player) - self._rate_set(sender, "强行突破") + self._rate_set(sender, roomid or "", "强行突破") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 强行突破失败,灵气反噬!", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) @@ -1196,7 +1238,7 @@ class XiuxianPlugin(MessagePluginInterface): pass self._save_player(defender) self._save_player(attacker) - self._rate_set(sender, "劫掠") + self._rate_set(sender, roomid or "", "劫掠") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 劫掠成功,获得灵石{gain}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1206,7 +1248,7 @@ class XiuxianPlugin(MessagePluginInterface): self.revoke.add_message_to_revoke(roomid, g_client_msg_id, g_create_time, g_new_msg_id, 10) return True, "劫掠成功" else: - self._rate_set(sender, "劫掠") + self._rate_set(sender, roomid or "", "劫掠") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 劫掠失败", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) @@ -1256,7 +1298,7 @@ class XiuxianPlugin(MessagePluginInterface): # 使用_save_player确保同时保存到Redis和MariaDB self._save_player(giver) self._save_player(receiver) - self._rate_set(sender, "赠与") + self._rate_set(sender, roomid or "", "赠与") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已向 {target} 赠与灵石 {qty}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1307,7 +1349,7 @@ class XiuxianPlugin(MessagePluginInterface): self.redis_db.invalidate_player(target, receiver.get("group_id", "")) self._save_player(giver) self._save_player(receiver) - self._rate_set(sender, "赠送") + self._rate_set(sender, roomid or "", "赠送") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已向 {target} 赠送 {item_name} × {qty}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1350,7 +1392,7 @@ class XiuxianPlugin(MessagePluginInterface): except Exception: pass self._save_player(player) - self._rate_set(sender, "创建门派") + self._rate_set(sender, roomid or "", "创建门派") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 门派已创建:{name}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1385,7 +1427,7 @@ class XiuxianPlugin(MessagePluginInterface): except Exception: pass self._save_player(player) - self._rate_set(sender, "加入门派") + self._rate_set(sender, roomid or "", "加入门派") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已加入门派:{name}", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10) @@ -1406,7 +1448,7 @@ class XiuxianPlugin(MessagePluginInterface): except Exception: pass self._save_player(player) - self._rate_set(sender, "退出门派") + self._rate_set(sender, roomid or "", "退出门派") client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "✅ 已退出门派", sender) if self.revoke: self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)