- main.py: 4360 → 146 lines (96.6% reduction), entry layer only - services/: rate_limiter, autostart, persona, connection, profile, hotspot, content, engagement, scheduler, queue_ops (10 business modules) - ui/app.py: all Gradio UI code extracted into build_app(cfg, analytics) - Fix: with gr.Blocks() indented inside build_app function - Fix: cfg.all property (not get_all method) - Fix: STATUS_LABELS, get_persona_keywords, fetch_proactive_notes imports - Fix: queue_ops module-level set_publish_callback moved into configure() - Fix: pub_queue.format_*() wrapped as queue_format_table/calendar helpers - All 14 files syntax-verified, build_app() runtime-verified - 58/58 tasks complete"
111 lines
3.3 KiB
Python
111 lines
3.3 KiB
Python
"""
|
||
services/rate_limiter.py
|
||
频率控制、每日限额、冷却管理
|
||
"""
|
||
import time
|
||
import threading
|
||
from datetime import datetime
|
||
|
||
# ---- 操作记录:防重复 & 每日统计 ----
|
||
_op_history = {
|
||
"commented_feeds": set(), # 已评论的 feed_id
|
||
"replied_comments": set(), # 已回复的 comment_id
|
||
"liked_feeds": set(), # 已点赞的 feed_id
|
||
"favorited_feeds": set(), # 已收藏的 feed_id
|
||
}
|
||
_daily_stats = {
|
||
"date": "",
|
||
"comments": 0,
|
||
"likes": 0,
|
||
"favorites": 0,
|
||
"publishes": 0,
|
||
"replies": 0,
|
||
"errors": 0,
|
||
}
|
||
# 每日操作上限
|
||
DAILY_LIMITS = {
|
||
"comments": 30,
|
||
"likes": 80,
|
||
"favorites": 50,
|
||
"publishes": 8,
|
||
"replies": 40,
|
||
}
|
||
# 连续错误计数 → 冷却
|
||
_consecutive_errors = 0
|
||
_error_cooldown_until = 0.0
|
||
|
||
# 线程锁,保护 stats/history 并发写入
|
||
_stats_lock = threading.Lock()
|
||
|
||
|
||
def _reset_daily_stats_if_needed():
|
||
"""每天自动重置统计"""
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
if _daily_stats["date"] != today:
|
||
_daily_stats.update({
|
||
"date": today, "comments": 0, "likes": 0,
|
||
"favorites": 0, "publishes": 0, "replies": 0, "errors": 0,
|
||
})
|
||
# 每日重置历史记录(允许隔天重复互动)
|
||
for k in _op_history:
|
||
_op_history[k].clear()
|
||
|
||
|
||
def _check_daily_limit(op_type: str) -> bool:
|
||
"""检查是否超出每日限额"""
|
||
_reset_daily_stats_if_needed()
|
||
limit = DAILY_LIMITS.get(op_type, 999)
|
||
current = _daily_stats.get(op_type, 0)
|
||
return current < limit
|
||
|
||
|
||
def _increment_stat(op_type: str):
|
||
"""增加操作计数"""
|
||
_reset_daily_stats_if_needed()
|
||
_daily_stats[op_type] = _daily_stats.get(op_type, 0) + 1
|
||
|
||
|
||
def _record_error(log_fn=None):
|
||
"""记录错误,连续错误触发冷却。log_fn 可选,用于写入日志。"""
|
||
global _consecutive_errors, _error_cooldown_until
|
||
_consecutive_errors += 1
|
||
_daily_stats["errors"] = _daily_stats.get("errors", 0) + 1
|
||
if _consecutive_errors >= 3:
|
||
cooldown = min(60 * _consecutive_errors, 600) # 最多冷却10分钟
|
||
_error_cooldown_until = time.time() + cooldown
|
||
if log_fn:
|
||
log_fn(f"⚠️ 连续 {_consecutive_errors} 次错误,冷却 {cooldown}s")
|
||
|
||
|
||
def _clear_error_streak():
|
||
"""操作成功后清除连续错误记录"""
|
||
global _consecutive_errors
|
||
_consecutive_errors = 0
|
||
|
||
|
||
def _is_in_cooldown() -> bool:
|
||
"""检查是否在错误冷却期"""
|
||
return time.time() < _error_cooldown_until
|
||
|
||
|
||
def _is_in_operating_hours(start_hour: int = 7, end_hour: int = 23) -> bool:
|
||
"""检查是否在运营时间段"""
|
||
now_hour = datetime.now().hour
|
||
return start_hour <= now_hour < end_hour
|
||
|
||
|
||
def _get_stats_summary() -> str:
|
||
"""获取今日运营统计摘要"""
|
||
_reset_daily_stats_if_needed()
|
||
s = _daily_stats
|
||
lines = [
|
||
f"📊 **今日运营统计** ({s['date']})",
|
||
f"- 💬 评论: {s['comments']}/{DAILY_LIMITS['comments']}",
|
||
f"- ❤️ 点赞: {s['likes']}/{DAILY_LIMITS['likes']}",
|
||
f"- ⭐ 收藏: {s['favorites']}/{DAILY_LIMITS['favorites']}",
|
||
f"- 🚀 发布: {s['publishes']}/{DAILY_LIMITS['publishes']}",
|
||
f"- 💌 回复: {s['replies']}/{DAILY_LIMITS['replies']}",
|
||
f"- ❌ 错误: {s['errors']}",
|
||
]
|
||
return "\n".join(lines)
|