- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from datetime import datetime, timezone
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class BuiltinActivationCode:
|
||
"""内置激活码:固定自然月有效期,过期后不可再激活。"""
|
||
|
||
code: str
|
||
label: str # 如 2026-01,便于识别
|
||
expires_at: str # ISO8601 UTC,到达时刻起作废
|
||
|
||
|
||
def _month_label(year: int, month: int) -> str:
|
||
return f"{year}-{month:02d}"
|
||
|
||
|
||
def _month_expiry(year: int, month: int) -> str:
|
||
"""每月 1 日 0 点(UTC)为界:该月码在下一月 1 日 0 点作废。"""
|
||
if month == 12:
|
||
end_year, end_month = year + 1, 1
|
||
else:
|
||
end_year, end_month = year, month + 1
|
||
dt = datetime(end_year, end_month, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||
return dt.isoformat()
|
||
|
||
|
||
# 12 个 GUID 激活码:2026-05 起连续 12 个月(至 2027-04),过期作废
|
||
_BUILTIN_CODE_GUIDS = (
|
||
"2A76F0DD-F7A0-480D-B0B2-8B4B41C283AA",
|
||
"12205DF6-02DE-4054-8C68-564543727A0F",
|
||
"729754A0-99BF-49AA-970A-2C3BFE793E1E",
|
||
"DCCE3DD5-C843-4C59-B033-CAA7E8DBA618",
|
||
"0CAC138E-8635-4511-8A8D-2EDBB55F60A3",
|
||
"4AEFA1D2-E852-41F1-880A-0EC76C544DFC",
|
||
"C2FC0006-28A7-4018-8DF3-CC7A589CFAB6",
|
||
"6C0F914D-CB48-4EF0-A5A4-A954FD2C48E2",
|
||
"488D70D7-472D-44EC-9573-AA40E1FFC2F8",
|
||
"C79718F6-6828-413B-98DA-42A0BC323D70",
|
||
"E1A4FA71-E258-4FE6-B60E-D4CA6831D0B2",
|
||
"C0DAA98A-678A-4F6D-A04B-38F824D104A8",
|
||
)
|
||
|
||
_START_YEAR = 2026
|
||
_START_MONTH = 5
|
||
|
||
|
||
def _builtin_month_sequence() -> list[tuple[int, int]]:
|
||
months: list[tuple[int, int]] = []
|
||
year, month = _START_YEAR, _START_MONTH
|
||
for _ in range(len(_BUILTIN_CODE_GUIDS)):
|
||
months.append((year, month))
|
||
if month == 12:
|
||
year, month = year + 1, 1
|
||
else:
|
||
month += 1
|
||
return months
|
||
|
||
|
||
BUILTIN_ACTIVATION_CODES: tuple[BuiltinActivationCode, ...] = tuple(
|
||
BuiltinActivationCode(
|
||
guid,
|
||
_month_label(year, month),
|
||
_month_expiry(year, month),
|
||
)
|
||
for guid, (year, month) in zip(_BUILTIN_CODE_GUIDS, _builtin_month_sequence(), strict=True)
|
||
)
|
||
|
||
_CODE_INDEX: dict[str, BuiltinActivationCode] = {
|
||
entry.code: entry for entry in BUILTIN_ACTIVATION_CODES
|
||
}
|
||
|
||
|
||
def normalize_activation_code(code: str) -> str:
|
||
text = code.strip().upper().replace(" ", "")
|
||
hex_only = text.replace("-", "")
|
||
if len(hex_only) == 32 and all(c in "0123456789ABCDEF" for c in hex_only):
|
||
return (
|
||
f"{hex_only[0:8]}-{hex_only[8:12]}-{hex_only[12:16]}-"
|
||
f"{hex_only[16:20]}-{hex_only[20:32]}"
|
||
)
|
||
return text
|
||
|
||
|
||
def lookup_builtin_code(code: str) -> BuiltinActivationCode | None:
|
||
return _CODE_INDEX.get(normalize_activation_code(code))
|
||
|
||
|
||
def get_builtin_code_for_period(period_label: str | None = None) -> BuiltinActivationCode | None:
|
||
"""按月份标签(如 2026-05)查找内置激活码;默认当前 UTC 月。"""
|
||
label = period_label or current_period_label()
|
||
for entry in BUILTIN_ACTIVATION_CODES:
|
||
if entry.label == label:
|
||
return entry
|
||
return None
|
||
|
||
|
||
def current_period_label(*, now: datetime | None = None) -> str:
|
||
"""当前自然月标签(UTC),与 BuiltinActivationCode.label 一致。"""
|
||
moment = now or datetime.now(timezone.utc)
|
||
if moment.tzinfo is None:
|
||
moment = moment.replace(tzinfo=timezone.utc)
|
||
else:
|
||
moment = moment.astimezone(timezone.utc)
|
||
return _month_label(moment.year, moment.month)
|
||
|
||
|
||
def is_builtin_code_current_month(
|
||
entry: BuiltinActivationCode, *, now: datetime | None = None
|
||
) -> bool:
|
||
return entry.label == current_period_label(now=now)
|
||
|
||
|
||
def is_builtin_code_expired(entry: BuiltinActivationCode, *, now: datetime | None = None) -> bool:
|
||
moment = now or datetime.now(timezone.utc)
|
||
exp = datetime.fromisoformat(entry.expires_at.replace("Z", "+00:00"))
|
||
if exp.tzinfo is None:
|
||
exp = exp.replace(tzinfo=timezone.utc)
|
||
return moment >= exp
|