140 lines
4.5 KiB
Python
140 lines
4.5 KiB
Python
"""平台配置、别名解析、手机号校验。"""
|
||
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)
|