"""平台配置、别名解析、手机号校验。""" import re # 平台唯一配置表:只改这里即可。键 = 入库的 platform;label = 帮助/提示里的展示名; # prefix = 默认账号名「{prefix}{序号}号」,省略时等于 label; # aliases = 额外 CLI 称呼(英文键与 label 已自动参与解析,不必重复写)。 # 登录检测:仅看页面 DOM。匹配 anchor 的标签页上出现「未登录」选择器则未登录;否则视为已登录。 # - _LOGIN_LOGGED_OUT_DOM_GENERIC_SELECTORS:通用「登录」按钮/链接等(可被 login_skip_generic_logged_out_dom 关闭)。 # - login_logged_out_selectors:按平台追加;误判时也可用 login_skip_generic_logged_out_dom + 仅平台自选。 PLATFORMS = { "sohu": { "url": "https://mp.sohu.com", "label": "搜狐号", "prefix": "搜狐", "aliases": ["搜狐"], }, "toutiao": { "url": "https://mp.toutiao.com/", "label": "头条号", "prefix": "头条", "aliases": ["头条"], }, "zhihu": { "url": "https://www.zhihu.com", "label": "知乎", "aliases": ["知乎号"], }, "wechat": { "url": "https://mp.weixin.qq.com", "label": "微信公众号", "prefix": "微信", "aliases": ["公众号", "微信"], }, "kimi": { "url": "https://kimi.moonshot.cn", "label": "Kimi", "aliases": ["月之暗面"], }, "deepseek": { "url": "https://chat.deepseek.com", "label": "DeepSeek", }, "doubao": { "url": "https://www.doubao.com", "label": "豆包", }, "qianwen": { "url": "https://tongyi.aliyun.com", "label": "通义千问", "prefix": "通义", "aliases": ["通义", "千问"], }, "yiyan": { "url": "https://yiyan.baidu.com", "label": "文心一言", "prefix": "文心", "aliases": ["文心", "一言"], }, "yuanbao": { "url": "https://yuanbao.tencent.com", "label": "腾讯元宝", "prefix": "元宝", "aliases": ["元宝"], }, } # 未登录页共性:主行动点含「登录」——submit 按钮、Element 主按钮、Semi 文案、标题、通栏主按钮等,Playwright :has-text 可覆盖多数站点。 _LOGIN_LOGGED_OUT_DOM_GENERIC_SELECTORS = ( 'button:has-text("登录")', 'role=button[name="登录"]', 'a:has-text("登录")', 'h4:has-text("登录")', 'span.semi-button-content:has-text("登录")', ) def _build_platform_derived(): urls = {} name_prefix = {} primary_cn = {} alias_to_key = {} def _register_alias(alias: str, key: str) -> None: if not alias or not str(alias).strip(): return a = str(alias).strip() if a not in alias_to_key: alias_to_key[a] = key lo = a.lower() if lo not in alias_to_key: alias_to_key[lo] = key for key, spec in PLATFORMS.items(): urls[key] = spec["url"] primary_cn[key] = spec["label"] name_prefix[key] = (spec.get("prefix") or spec["label"]).strip() or key _register_alias(key, key) _register_alias(spec["label"], key) for a in spec.get("aliases") or []: _register_alias(a, key) return urls, name_prefix, primary_cn, alias_to_key PLATFORM_URLS, _PLATFORM_NAME_CN, _PLATFORM_PRIMARY_CN, _PLATFORM_ALIAS_TO_KEY = _build_platform_derived() def resolve_platform_key(name: str): """将用户输入解析为内部 platform 键;无法识别返回 None。""" if name is None: return None s = str(name).strip() if not s: return None sl = s.lower() if sl in PLATFORM_URLS: return sl return _PLATFORM_ALIAS_TO_KEY.get(s) def _normalize_phone_digits(phone: str) -> str: """从输入中提取数字串,供号段规则校验与入库去重(不对外承诺可随意夹杂符号)。""" d = re.sub(r"\D", "", (phone or "").strip()) if d.startswith("86") and len(d) >= 13: d = d[2:] return d def _is_valid_cn_mobile11(digits: str) -> bool: """中国大陆 11 位手机号:1 开头,第二位 3–9,共 11 位数字。""" return bool(re.fullmatch(r"1[3-9]\d{9}", digits or "")) def _platform_list_cn_for_help() -> str: """帮助文案:一行中文展示名(不重复内部键)。""" parts = [] for k in sorted(PLATFORM_URLS.keys()): parts.append(_PLATFORM_PRIMARY_CN.get(k, k)) return "、".join(parts)