Some checks failed
SDK CI / python-compile (push) Has been cancelled
- 新增 sdk/app/agent(Hook/Skills/Anti-ban 等)及 NAS/ADB 脚本 - 更新 Android 端、unified、PHP SDK、Docker Compose - Soul 文档迁移至 Soul调研/;移除资料目录内 APK - .gitignore 排除 sdk/tmp、sdk/logs、sdk/tmp_rom Co-authored-by: Cursor <cursoragent@cursor.com>
251 lines
8.4 KiB
Python
251 lines
8.4 KiB
Python
"""
|
|
养号调度器 — 新号冷启动期自动限制操作量,渐进提升
|
|
|
|
功能:
|
|
1. 新号冷启动期 (前 7 天) 自动限流
|
|
2. 每日操作量递增曲线
|
|
3. 模拟日常行为 (刷朋友圈、看文章、回消息)
|
|
4. 活跃时段分布 (早 8-10, 午 12-14, 晚 19-22)
|
|
5. 与 RiskSentinel 联动调整阈值
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import random
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NurtureScheduler:
|
|
"""养号调度器 — 安全地养熟一个账号"""
|
|
|
|
COLD_START_DAYS = 7
|
|
|
|
DAILY_LIMITS_CURVE = {
|
|
1: {"send_message": 5, "add_friend": 2, "moment_like": 5, "moment_post": 0},
|
|
2: {"send_message": 10, "add_friend": 3, "moment_like": 8, "moment_post": 1},
|
|
3: {"send_message": 18, "add_friend": 5, "moment_like": 12, "moment_post": 1},
|
|
4: {"send_message": 25, "add_friend": 8, "moment_like": 18, "moment_post": 2},
|
|
5: {"send_message": 35, "add_friend": 12, "moment_like": 22, "moment_post": 2},
|
|
6: {"send_message": 45, "add_friend": 15, "moment_like": 28, "moment_post": 3},
|
|
7: {"send_message": 50, "add_friend": 18, "moment_like": 30, "moment_post": 3},
|
|
}
|
|
|
|
MATURE_LIMITS = {
|
|
"send_message": 60, "add_friend": 20, "moment_like": 30,
|
|
"moment_post": 5, "group_send": 10, "profile_view": 40,
|
|
}
|
|
|
|
ACTIVE_HOURS: List[Tuple[int, int, float]] = [
|
|
(7, 9, 0.6),
|
|
(9, 12, 0.8),
|
|
(12, 14, 1.0),
|
|
(14, 17, 0.7),
|
|
(17, 19, 0.5),
|
|
(19, 22, 1.0),
|
|
(22, 24, 0.4),
|
|
]
|
|
|
|
DAILY_BEHAVIORS = [
|
|
{"action": "browse_moments", "weight": 3, "duration_min": (2, 8)},
|
|
{"action": "read_article", "weight": 2, "duration_min": (1, 5)},
|
|
{"action": "check_messages", "weight": 4, "duration_min": (1, 3)},
|
|
{"action": "browse_discover", "weight": 1, "duration_min": (2, 6)},
|
|
]
|
|
|
|
def __init__(self, state_file: str = "nurture_state.json"):
|
|
self.state_file = state_file
|
|
self._state = self._load_state()
|
|
|
|
def register_account(self, account_id: str):
|
|
"""注册一个新的养号账号"""
|
|
if account_id not in self._state:
|
|
self._state[account_id] = {
|
|
"start_date": datetime.now().isoformat(),
|
|
"day_counters": {},
|
|
"total_ops": {},
|
|
}
|
|
self._save_state()
|
|
logger.info(f"注册养号: {account_id}")
|
|
|
|
def get_account_day(self, account_id: str) -> int:
|
|
"""获取账号处于第几天"""
|
|
info = self._state.get(account_id)
|
|
if not info:
|
|
return 999 # 未注册视为成熟号
|
|
start = datetime.fromisoformat(info["start_date"])
|
|
delta = (datetime.now() - start).days + 1
|
|
return delta
|
|
|
|
def is_cold_start(self, account_id: str) -> bool:
|
|
"""是否在冷启动期"""
|
|
return self.get_account_day(account_id) <= self.COLD_START_DAYS
|
|
|
|
def get_daily_limit(self, account_id: str, action: str) -> int:
|
|
"""获取该账号今日某操作的上限"""
|
|
day = self.get_account_day(account_id)
|
|
if day > self.COLD_START_DAYS:
|
|
return self.MATURE_LIMITS.get(action, 100)
|
|
day_limits = self.DAILY_LIMITS_CURVE.get(day, self.DAILY_LIMITS_CURVE[7])
|
|
return day_limits.get(action, self.MATURE_LIMITS.get(action, 100))
|
|
|
|
def can_operate(self, account_id: str, action: str) -> dict:
|
|
"""
|
|
检查账号当前是否允许执行某操作
|
|
|
|
Returns:
|
|
{"allowed": bool, "reason": str, "day": int, "limit": int, "used": int}
|
|
"""
|
|
day = self.get_account_day(account_id)
|
|
limit = self.get_daily_limit(account_id, action)
|
|
|
|
today_key = datetime.now().strftime("%Y-%m-%d")
|
|
info = self._state.get(account_id, {})
|
|
counters = info.get("day_counters", {}).get(today_key, {})
|
|
used = counters.get(action, 0)
|
|
|
|
if not self._is_active_hour():
|
|
return {
|
|
"allowed": False,
|
|
"reason": "当前不在活跃时段",
|
|
"day": day,
|
|
"limit": limit,
|
|
"used": used,
|
|
}
|
|
|
|
if used >= limit:
|
|
return {
|
|
"allowed": False,
|
|
"reason": f"今日已达上限 ({used}/{limit})",
|
|
"day": day,
|
|
"limit": limit,
|
|
"used": used,
|
|
}
|
|
|
|
return {
|
|
"allowed": True,
|
|
"reason": "",
|
|
"day": day,
|
|
"limit": limit,
|
|
"used": used,
|
|
}
|
|
|
|
def record_operation(self, account_id: str, action: str):
|
|
"""记录一次操作"""
|
|
if account_id not in self._state:
|
|
self.register_account(account_id)
|
|
|
|
today_key = datetime.now().strftime("%Y-%m-%d")
|
|
info = self._state[account_id]
|
|
|
|
if "day_counters" not in info:
|
|
info["day_counters"] = {}
|
|
if today_key not in info["day_counters"]:
|
|
info["day_counters"][today_key] = {}
|
|
|
|
counters = info["day_counters"][today_key]
|
|
counters[action] = counters.get(action, 0) + 1
|
|
|
|
if "total_ops" not in info:
|
|
info["total_ops"] = {}
|
|
info["total_ops"][action] = info["total_ops"].get(action, 0) + 1
|
|
|
|
self._save_state()
|
|
|
|
def get_nurture_plan(self, account_id: str) -> List[dict]:
|
|
"""
|
|
生成今日养号行为计划 — 穿插在正式操作之间执行
|
|
"""
|
|
plan = []
|
|
hour = datetime.now().hour
|
|
activity_weight = self._get_hour_weight(hour)
|
|
|
|
behavior_count = max(1, int(random.uniform(2, 5) * activity_weight))
|
|
selected = random.choices(
|
|
self.DAILY_BEHAVIORS,
|
|
weights=[b["weight"] for b in self.DAILY_BEHAVIORS],
|
|
k=behavior_count,
|
|
)
|
|
|
|
for behavior in selected:
|
|
dur_min = random.uniform(*behavior["duration_min"])
|
|
delay_min = random.uniform(5, 30)
|
|
plan.append({
|
|
"action": behavior["action"],
|
|
"duration_sec": int(dur_min * 60),
|
|
"delay_before_sec": int(delay_min * 60),
|
|
"hour_weight": activity_weight,
|
|
})
|
|
|
|
return plan
|
|
|
|
def get_risk_sentinel_overrides(self, account_id: str) -> Dict[str, Tuple[int, int]]:
|
|
"""
|
|
返回适合该账号当前阶段的 RiskSentinel 阈值覆盖
|
|
|
|
用法: sentinel = RiskSentinel(custom_limits=scheduler.get_risk_sentinel_overrides(acct))
|
|
"""
|
|
overrides = {}
|
|
for action in self.MATURE_LIMITS:
|
|
limit = self.get_daily_limit(account_id, action)
|
|
overrides[action] = (3600, limit)
|
|
return overrides
|
|
|
|
def get_stats(self, account_id: str) -> dict:
|
|
"""获取养号统计"""
|
|
info = self._state.get(account_id)
|
|
if not info:
|
|
return {"registered": False}
|
|
|
|
day = self.get_account_day(account_id)
|
|
today_key = datetime.now().strftime("%Y-%m-%d")
|
|
counters = info.get("day_counters", {}).get(today_key, {})
|
|
|
|
return {
|
|
"registered": True,
|
|
"account_id": account_id,
|
|
"day": day,
|
|
"cold_start": day <= self.COLD_START_DAYS,
|
|
"today_counters": counters,
|
|
"total_ops": info.get("total_ops", {}),
|
|
"start_date": info["start_date"],
|
|
}
|
|
|
|
# ---- 内部方法 ----
|
|
|
|
def _is_active_hour(self) -> bool:
|
|
"""当前是否在活跃时段"""
|
|
hour = datetime.now().hour
|
|
for start, end, weight in self.ACTIVE_HOURS:
|
|
if start <= hour < end and weight >= 0.3:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def _get_hour_weight(hour: int) -> float:
|
|
"""获取当前小时的活跃权重"""
|
|
for start, end, weight in NurtureScheduler.ACTIVE_HOURS:
|
|
if start <= hour < end:
|
|
return weight
|
|
return 0.1
|
|
|
|
def _load_state(self) -> dict:
|
|
if os.path.exists(self.state_file):
|
|
try:
|
|
with open(self.state_file, "r") as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
logger.warning(f"加载养号状态失败: {e}")
|
|
return {}
|
|
|
|
def _save_state(self):
|
|
try:
|
|
with open(self.state_file, "w") as f:
|
|
json.dump(self._state, f, ensure_ascii=False, indent=2)
|
|
except Exception as e:
|
|
logger.warning(f"保存养号状态失败: {e}")
|