fix: validate platform keys at the gate and stop auto-register
All checks were successful
技能自动化发布 / release (push) Successful in 6s
All checks were successful
技能自动化发布 / release (push) Successful in 6s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -261,7 +261,7 @@ def main() -> None:
|
||||
args = p.parse_args(av[3:])
|
||||
sys.exit(cmd_platform_ensure(
|
||||
key=args.key,
|
||||
display_name=args.display_name or args.key,
|
||||
display_name=args.display_name,
|
||||
domain=args.domain,
|
||||
url=args.url,
|
||||
auth_strategy=args.auth_strategy,
|
||||
|
||||
@@ -10,10 +10,8 @@ from typing import Optional
|
||||
from db.accounts_repo import (
|
||||
insert_account,
|
||||
resolve_auth_strategy_for_platform,
|
||||
ensure_platform_from_caller_meta,
|
||||
)
|
||||
from db.auth_strategy import (
|
||||
ALL_AUTH_STRATEGIES,
|
||||
AUTH_STRATEGY_CLIENT_CERTIFICATE,
|
||||
AUTH_STRATEGY_DEVICE_BOUND,
|
||||
AUTH_STRATEGY_PASSWORD_PLUS_2FA,
|
||||
@@ -22,14 +20,10 @@ from db.auth_strategy import (
|
||||
AUTH_STRATEGY_SSO_REDIRECT,
|
||||
)
|
||||
from db.connection import get_conn, init_db
|
||||
from util.logging_config import get_skill_logger
|
||||
from util.platforms import (
|
||||
DEFAULT_CAPABILITIES_JSON,
|
||||
PlatformResolveResult,
|
||||
_platform_list_cn_for_help,
|
||||
is_valid_platform_key_format,
|
||||
resolve_platform_key_dynamic,
|
||||
seed_platforms,
|
||||
)
|
||||
|
||||
|
||||
@@ -137,7 +131,7 @@ def _remove_profile_dir(path: str) -> None:
|
||||
|
||||
@dataclass
|
||||
class PlatformCallerMeta:
|
||||
"""调用方为未注册自定义平台提供的自动注册元数据(pick-web / add-web)。"""
|
||||
"""调用方可附带的平台展示信息。不再用于自动插入 platforms 行。"""
|
||||
|
||||
display_name: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
@@ -145,8 +139,17 @@ class PlatformCallerMeta:
|
||||
auth_strategy: Optional[str] = None
|
||||
aliases: list[str] = field(default_factory=list)
|
||||
|
||||
def is_sufficient_for_auto_register(self) -> bool:
|
||||
return bool((self.display_name or "").strip() and (self.url or "").strip())
|
||||
|
||||
_RESOLVE_CODES_WITHOUT_PLATFORM_LIST = frozenset(
|
||||
{
|
||||
"PLATFORM_NOT_REGISTERED",
|
||||
"INVALID_PLATFORM_KEY",
|
||||
"PLATFORM_DISABLED",
|
||||
"AMBIGUOUS_PLATFORM",
|
||||
"PLATFORM_ALIAS_CONFLICT",
|
||||
"INVALID_PLATFORM_DISPLAY_NAME",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _emit_platform_resolve_error(result: PlatformResolveResult, hint: str = "") -> None:
|
||||
@@ -164,77 +167,9 @@ def _resolve_or_auto_register(
|
||||
*,
|
||||
hint: str = "",
|
||||
) -> Optional[str]:
|
||||
"""解析平台;未注册时可在元数据充足时自动 ensure(幂等、仅填空字段)。"""
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
try:
|
||||
result = resolve_platform_key_dynamic(platform_input, conn=conn)
|
||||
if result.ok:
|
||||
return result.platform_key
|
||||
|
||||
if result.error_code in ("PLATFORM_DISABLED", "INVALID_PLATFORM_KEY", "INVALID_PLATFORM"):
|
||||
_emit_platform_resolve_error(result, hint=hint)
|
||||
return None
|
||||
|
||||
if result.error_code != "PLATFORM_NOT_REGISTERED":
|
||||
_emit_platform_resolve_error(result, hint=hint)
|
||||
if result.error_code not in (
|
||||
"PLATFORM_NOT_REGISTERED",
|
||||
"INVALID_PLATFORM_KEY",
|
||||
"PLATFORM_DISABLED",
|
||||
):
|
||||
_say("支持:" + _platform_list_cn_for_help())
|
||||
return None
|
||||
|
||||
raw_key = (platform_input or "").strip().lower()
|
||||
if not is_valid_platform_key_format(raw_key):
|
||||
_emit_platform_resolve_error(
|
||||
PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM_KEY",
|
||||
message=result.message or f"平台 key 格式非法:「{platform_input}」。",
|
||||
),
|
||||
hint=hint,
|
||||
)
|
||||
return None
|
||||
|
||||
if not meta or not meta.is_sufficient_for_auto_register():
|
||||
_emit_platform_resolve_error(
|
||||
PlatformResolveResult(
|
||||
error_code="PLATFORM_NOT_REGISTERED",
|
||||
message=(
|
||||
f"平台 {raw_key} 尚未注册。"
|
||||
"请传入 --platform-display-name 与 --platform-url,"
|
||||
"或先执行 account platform ensure。"
|
||||
),
|
||||
),
|
||||
hint=hint,
|
||||
)
|
||||
return None
|
||||
|
||||
if meta.auth_strategy and meta.auth_strategy not in ALL_AUTH_STRATEGIES:
|
||||
_say(_err_json(
|
||||
"ERR_AUTH_STRATEGY_NOT_SUPPORTED",
|
||||
f"不支持的 --platform-auth-strategy:{meta.auth_strategy}",
|
||||
))
|
||||
return None
|
||||
|
||||
seed_platforms(conn)
|
||||
action = ensure_platform_from_caller_meta(
|
||||
conn,
|
||||
raw_key,
|
||||
display_name=(meta.display_name or "").strip(),
|
||||
domain=(meta.domain or "generic").strip() or "generic",
|
||||
default_url=(meta.url or "").strip(),
|
||||
aliases=meta.aliases,
|
||||
default_auth_strategy=meta.auth_strategy,
|
||||
capabilities_json=DEFAULT_CAPABILITIES_JSON,
|
||||
)
|
||||
get_skill_logger().info(
|
||||
"platform_auto_registered key=%s action=%s", raw_key, action
|
||||
)
|
||||
return raw_key
|
||||
finally:
|
||||
conn.close()
|
||||
"""解析已注册平台;未注册则拒绝。meta 仅兼容旧调用方,不再自动建平台。"""
|
||||
del meta
|
||||
return _resolve_or_fail(platform_input, hint=hint)
|
||||
|
||||
|
||||
def _resolve_or_fail(input_name: str, hint: str = "") -> Optional[str]:
|
||||
@@ -250,6 +185,6 @@ def _resolve_or_fail(input_name: str, hint: str = "") -> Optional[str]:
|
||||
return result.platform_key
|
||||
|
||||
_emit_platform_resolve_error(result, hint=hint)
|
||||
if result.error_code not in ("PLATFORM_NOT_REGISTERED", "INVALID_PLATFORM_KEY", "PLATFORM_DISABLED"):
|
||||
if result.error_code not in _RESOLVE_CODES_WITHOUT_PLATFORM_LIST:
|
||||
_say("支持:" + _platform_list_cn_for_help())
|
||||
return None
|
||||
|
||||
@@ -8,7 +8,7 @@ from db.connection import get_conn, init_db
|
||||
from service._svc_common import _err_json, _ok_json, _resolve_or_fail, _say
|
||||
from util.constants import SKILL_SLUG
|
||||
from util.logging_config import get_skill_logger
|
||||
from util.platforms import DEFAULT_CAPABILITIES_JSON, is_valid_platform_key_format, seed_platforms
|
||||
from util.platforms import DEFAULT_CAPABILITIES_JSON, seed_platforms
|
||||
from util.runtime_paths import get_db_path
|
||||
|
||||
|
||||
@@ -76,6 +76,20 @@ def cmd_platform_get(input_key: str) -> None:
|
||||
))
|
||||
|
||||
|
||||
def _aliases_for_ensure(aliases: Optional[str], key: str, display_name: str) -> tuple[list[str], str]:
|
||||
from util.platforms import _parse_aliases_json
|
||||
|
||||
claimed: list[str] = []
|
||||
extra = _parse_aliases_json(aliases) if aliases else []
|
||||
for item in (display_name, key, *extra):
|
||||
text = (item or "").strip()
|
||||
if text and text not in claimed:
|
||||
claimed.append(text)
|
||||
if aliases:
|
||||
return claimed, aliases
|
||||
return claimed, json.dumps(claimed, ensure_ascii=False)
|
||||
|
||||
|
||||
def cmd_platform_ensure(
|
||||
key: str,
|
||||
display_name: str,
|
||||
@@ -89,25 +103,33 @@ def cmd_platform_ensure(
|
||||
幂等地注册或更新平台到 DB。
|
||||
输出单行 JSON:{"ok": true, "platform_key": "xxx", "action": "created|updated"}
|
||||
"""
|
||||
from util.platform_validate import validate_platform_registration
|
||||
|
||||
log = get_skill_logger()
|
||||
key = (key or "").strip()
|
||||
display_name = (display_name or key).strip()
|
||||
display_name = (display_name or "").strip()
|
||||
if not key:
|
||||
_say('ERROR:INVALID_ARGS platform ensure 需要 --key 参数')
|
||||
return 1
|
||||
if not is_valid_platform_key_format(key):
|
||||
_say(_err_json(
|
||||
"ERROR:INVALID_PLATFORM_KEY",
|
||||
f"平台 key 格式非法:「{key}」。建议使用小写字母、数字、下划线或连字符。",
|
||||
))
|
||||
return 1
|
||||
|
||||
aliases_json = aliases if aliases else json.dumps([key, display_name], ensure_ascii=False)
|
||||
capabilities_json = capabilities if capabilities else DEFAULT_CAPABILITIES_JSON
|
||||
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
try:
|
||||
alias_list, aliases_json = _aliases_for_ensure(aliases, key, display_name)
|
||||
check = validate_platform_registration(
|
||||
key,
|
||||
display_name,
|
||||
aliases=alias_list,
|
||||
conn=conn,
|
||||
)
|
||||
if not check.ok:
|
||||
_say(_err_json(
|
||||
f"ERROR:{check.error_code}",
|
||||
check.message or "平台登记校验失败。",
|
||||
))
|
||||
return 1
|
||||
|
||||
capabilities_json = capabilities if capabilities else DEFAULT_CAPABILITIES_JSON
|
||||
existed = bool(
|
||||
conn.execute(
|
||||
"SELECT 1 FROM platforms WHERE platform_key = ?", (key,)
|
||||
|
||||
264
scripts/util/platform_validate.py
Normal file
264
scripts/util/platform_validate.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""平台键 / 展示名校验:引用已有平台 vs 登记新平台。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
from util.platforms import (
|
||||
PLATFORMS,
|
||||
PlatformResolveResult,
|
||||
_parse_aliases_json,
|
||||
is_valid_platform_key_format,
|
||||
)
|
||||
|
||||
_ASCII_TOKEN = re.compile(r"[A-Za-z0-9]")
|
||||
|
||||
|
||||
def msg_empty() -> str:
|
||||
return "平台名不能为空。请传平台键(如 douyin、deepseek),不要传空字符串。"
|
||||
|
||||
|
||||
def msg_invalid_key(raw: str) -> str:
|
||||
return (
|
||||
f"平台键不合法:「{raw}」。\n"
|
||||
"平台键必须是小写英文/数字/下划线或连字符,例如 douyin、deepseek。\n"
|
||||
"「抖音」「DeepSeek」是展示名,不能当键。请改成已有键,或先登记:\n"
|
||||
"platform ensure --key <键> --display-name <中文名>\n"
|
||||
"查看已有键:platform list"
|
||||
)
|
||||
|
||||
|
||||
def msg_unrecognized(raw: str) -> str:
|
||||
return (
|
||||
f"无法识别平台:「{raw}」。\n"
|
||||
"请传平台键(如 douyin),不要自创中文键。\n"
|
||||
"先执行 platform list 核对;若是新平台,用 "
|
||||
"platform ensure --key <键> --display-name <中文名> 登记后再调 add-web / ensure-web。"
|
||||
)
|
||||
|
||||
|
||||
def msg_not_registered(key: str) -> str:
|
||||
return (
|
||||
f"平台 `{key}` 尚未注册,已拒绝写入,以免产生不规范平台。\n"
|
||||
f"若确认是新平台:platform ensure --key {key} --display-name <中文名> [--url …]\n"
|
||||
"若其实是已有站:请改用 platform list 里的平台键。"
|
||||
)
|
||||
|
||||
|
||||
def msg_ambiguous(raw: str, keys: Iterable[str]) -> str:
|
||||
listed = "、".join(f"`{k}`" for k in keys)
|
||||
return f"平台「{raw}」对应多个键({listed}),请改传明确的平台键。"
|
||||
|
||||
|
||||
def msg_alias_conflict(alias: str, owner: str, key: str) -> str:
|
||||
return (
|
||||
f"别名「{alias}」已被平台 `{owner}` 占用,不能再登记到 `{key}`。"
|
||||
"请换展示名/别名,或复用已有平台键。"
|
||||
)
|
||||
|
||||
|
||||
def msg_display_name_required() -> str:
|
||||
return (
|
||||
"登记平台必须同时提供 --display-name(给人看的名称,可以是中文)。\n"
|
||||
"平台键与展示名要分开,例如 --key douyin --display-name 抖音。"
|
||||
)
|
||||
|
||||
|
||||
def msg_disabled(key: str) -> str:
|
||||
return f"平台 {key} 已禁用。"
|
||||
|
||||
|
||||
def _looks_like_attempted_key(raw: str) -> bool:
|
||||
return bool(_ASCII_TOKEN.search(raw or "")) and not is_valid_platform_key_format(raw)
|
||||
|
||||
|
||||
def _lookup_stored_key(conn, key_lower: str) -> Optional[tuple[str, int]]:
|
||||
"""Return (platform_key, enabled) for an exact key, builtin or DB."""
|
||||
needle = (key_lower or "").strip().lower()
|
||||
if not needle:
|
||||
return None
|
||||
if conn is not None:
|
||||
row = conn.execute(
|
||||
"SELECT platform_key, enabled FROM platforms WHERE lower(platform_key) = ?",
|
||||
(needle,),
|
||||
).fetchone()
|
||||
if row:
|
||||
return str(row[0]), int(row[1] or 0)
|
||||
if needle in PLATFORMS:
|
||||
return needle, 1
|
||||
return None
|
||||
|
||||
|
||||
def _iter_enabled_name_map(conn) -> list[tuple[str, set[str]]]:
|
||||
"""Each enabled platform with its display_name + aliases (not the key)."""
|
||||
out: list[tuple[str, set[str]]] = []
|
||||
seen: set[str] = set()
|
||||
if conn is not None:
|
||||
rows = conn.execute(
|
||||
"SELECT platform_key, enabled, display_name, aliases_json FROM platforms"
|
||||
).fetchall()
|
||||
for platform_key, enabled, display_name, aliases_json in rows:
|
||||
key = str(platform_key)
|
||||
seen.add(key)
|
||||
if not enabled:
|
||||
continue
|
||||
names: set[str] = set()
|
||||
if display_name:
|
||||
names.add(str(display_name).strip())
|
||||
for alias in _parse_aliases_json(aliases_json):
|
||||
names.add(alias)
|
||||
out.append((key, {n for n in names if n}))
|
||||
for key, spec in PLATFORMS.items():
|
||||
if key in seen:
|
||||
continue
|
||||
names = {str(spec.get("display_name") or "").strip()}
|
||||
for alias in spec.get("aliases") or []:
|
||||
names.add(str(alias).strip())
|
||||
out.append((key, {n for n in names if n}))
|
||||
return out
|
||||
|
||||
|
||||
def _alias_matches(raw: str, conn) -> list[str]:
|
||||
needle = (raw or "").strip()
|
||||
if not needle:
|
||||
return []
|
||||
lower = needle.lower()
|
||||
matches: list[str] = []
|
||||
for key, names in _iter_enabled_name_map(conn):
|
||||
lowered = {n.lower() for n in names}
|
||||
if needle in names or lower in lowered:
|
||||
if key not in matches:
|
||||
matches.append(key)
|
||||
return matches
|
||||
|
||||
|
||||
def _keys_claiming_label(label: str, conn) -> list[str]:
|
||||
"""Keys that already use this string as key, display_name, or alias."""
|
||||
text = (label or "").strip()
|
||||
if not text:
|
||||
return []
|
||||
owners: list[str] = []
|
||||
stored = _lookup_stored_key(conn, text.lower())
|
||||
if stored and stored[0] not in owners:
|
||||
owners.append(stored[0])
|
||||
for key in _alias_matches(text, conn):
|
||||
if key not in owners:
|
||||
owners.append(key)
|
||||
return owners
|
||||
|
||||
|
||||
def resolve_platform_ref(input_name: str, conn=None) -> PlatformResolveResult:
|
||||
"""引用已有平台:已有键优先,其次唯一展示名/别名。不创建平台行。"""
|
||||
raw = (input_name or "").strip()
|
||||
if not raw:
|
||||
return PlatformResolveResult(error_code="INVALID_PLATFORM", message=msg_empty())
|
||||
|
||||
close_conn = False
|
||||
if conn is None:
|
||||
from db.connection import get_conn, init_db
|
||||
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
close_conn = True
|
||||
try:
|
||||
if is_valid_platform_key_format(raw):
|
||||
stored = _lookup_stored_key(conn, raw)
|
||||
if stored:
|
||||
key, enabled = stored
|
||||
if not enabled:
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_DISABLED",
|
||||
message=msg_disabled(key),
|
||||
)
|
||||
return PlatformResolveResult(platform_key=key)
|
||||
|
||||
folded = raw.lower()
|
||||
if is_valid_platform_key_format(folded):
|
||||
stored = _lookup_stored_key(conn, folded)
|
||||
if stored:
|
||||
key, enabled = stored
|
||||
if not enabled:
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_DISABLED",
|
||||
message=msg_disabled(key),
|
||||
)
|
||||
return PlatformResolveResult(platform_key=key)
|
||||
|
||||
matches = _alias_matches(raw, conn)
|
||||
if len(matches) == 1:
|
||||
return PlatformResolveResult(platform_key=matches[0])
|
||||
if len(matches) > 1:
|
||||
return PlatformResolveResult(
|
||||
error_code="AMBIGUOUS_PLATFORM",
|
||||
message=msg_ambiguous(raw, matches),
|
||||
)
|
||||
|
||||
if is_valid_platform_key_format(raw):
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_NOT_REGISTERED",
|
||||
message=msg_not_registered(raw),
|
||||
)
|
||||
if _looks_like_attempted_key(raw) or is_valid_platform_key_format(folded):
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM_KEY",
|
||||
message=msg_invalid_key(raw),
|
||||
)
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM",
|
||||
message=msg_unrecognized(raw),
|
||||
)
|
||||
finally:
|
||||
if close_conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
def validate_platform_registration(
|
||||
key: str,
|
||||
display_name: str,
|
||||
aliases: Optional[Iterable[Any]] = None,
|
||||
conn=None,
|
||||
) -> PlatformResolveResult:
|
||||
"""登记/更新平台:键必须已是规范小写;展示名必填;别名不得抢别人的。"""
|
||||
raw_key = (key or "").strip()
|
||||
if not raw_key:
|
||||
return PlatformResolveResult(error_code="INVALID_PLATFORM", message=msg_empty())
|
||||
if not is_valid_platform_key_format(raw_key):
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM_KEY",
|
||||
message=msg_invalid_key(raw_key),
|
||||
)
|
||||
name = (display_name or "").strip()
|
||||
if not name:
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM_DISPLAY_NAME",
|
||||
message=msg_display_name_required(),
|
||||
)
|
||||
|
||||
close_conn = False
|
||||
if conn is None:
|
||||
from db.connection import get_conn, init_db
|
||||
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
close_conn = True
|
||||
try:
|
||||
claimed = [name]
|
||||
if aliases:
|
||||
for item in aliases:
|
||||
text = str(item).strip()
|
||||
if text:
|
||||
claimed.append(text)
|
||||
for label in claimed:
|
||||
if label.lower() == raw_key:
|
||||
continue
|
||||
owners = [k for k in _keys_claiming_label(label, conn) if k != raw_key]
|
||||
if owners:
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_ALIAS_CONFLICT",
|
||||
message=msg_alias_conflict(label, owners[0], raw_key),
|
||||
)
|
||||
return PlatformResolveResult(platform_key=raw_key)
|
||||
finally:
|
||||
if close_conn:
|
||||
conn.close()
|
||||
@@ -337,8 +337,8 @@ def resolve_platform_key(name: str) -> Optional[str]:
|
||||
|
||||
|
||||
def is_valid_platform_key_format(name: str) -> bool:
|
||||
"""平台 key 格式:^[a-z0-9][a-z0-9_-]{1,63}$"""
|
||||
s = (name or "").strip().lower()
|
||||
"""平台 key 格式:^[a-z0-9][a-z0-9_-]{1,63}$(不大小写折叠;登记必须本身已是小写)。"""
|
||||
s = (name or "").strip()
|
||||
if not s:
|
||||
return False
|
||||
return bool(PLATFORM_KEY_PATTERN.match(s))
|
||||
@@ -367,87 +367,11 @@ class PlatformResolveResult:
|
||||
return bool(self.platform_key) and not self.error_code
|
||||
|
||||
|
||||
def _resolve_platform_from_db(conn, raw: str) -> PlatformResolveResult:
|
||||
"""在 platforms 表中解析:精确 key(enabled=1)与 aliases_json。"""
|
||||
raw_stripped = (raw or "").strip()
|
||||
raw_lower = raw_stripped.lower()
|
||||
|
||||
if is_valid_platform_key_format(raw_stripped):
|
||||
row = conn.execute(
|
||||
"SELECT platform_key, enabled FROM platforms WHERE platform_key = ?",
|
||||
(raw_lower,),
|
||||
).fetchone()
|
||||
if row:
|
||||
if not row[1]:
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_DISABLED",
|
||||
message=f"平台 {row[0]} 已禁用。",
|
||||
)
|
||||
return PlatformResolveResult(platform_key=row[0])
|
||||
|
||||
rows = conn.execute(
|
||||
"SELECT platform_key, enabled, aliases_json FROM platforms"
|
||||
).fetchall()
|
||||
for platform_key, enabled, aliases_json in rows:
|
||||
if not enabled:
|
||||
continue
|
||||
names = {platform_key.lower()}
|
||||
for alias in _parse_aliases_json(aliases_json):
|
||||
names.add(alias.lower())
|
||||
names.add(alias)
|
||||
if raw_lower in names or raw_stripped in names:
|
||||
return PlatformResolveResult(platform_key=platform_key)
|
||||
|
||||
return PlatformResolveResult()
|
||||
|
||||
|
||||
def resolve_platform_key_dynamic(input_name: str, conn=None) -> PlatformResolveResult:
|
||||
"""
|
||||
统一平台解析:内置 PLATFORMS → DB platforms 表 → 错误分类。
|
||||
"""
|
||||
raw = (input_name or "").strip()
|
||||
if not raw:
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM",
|
||||
message="平台名不能为空。",
|
||||
)
|
||||
"""引用已有平台(键 / 唯一展示名或别名)。实现见 util.platform_validate。"""
|
||||
from util.platform_validate import resolve_platform_ref
|
||||
|
||||
builtin = resolve_platform_key(raw)
|
||||
if builtin:
|
||||
return PlatformResolveResult(platform_key=builtin)
|
||||
|
||||
close_conn = False
|
||||
if conn is None:
|
||||
from db.connection import get_conn, init_db
|
||||
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
close_conn = True
|
||||
try:
|
||||
db_result = _resolve_platform_from_db(conn, raw)
|
||||
if db_result.ok or db_result.error_code:
|
||||
return db_result
|
||||
finally:
|
||||
if close_conn:
|
||||
conn.close()
|
||||
|
||||
if not is_valid_platform_key_format(raw):
|
||||
return PlatformResolveResult(
|
||||
error_code="INVALID_PLATFORM_KEY",
|
||||
message=(
|
||||
f"平台 key 格式非法:「{raw}」。"
|
||||
"建议使用小写字母、数字、下划线或连字符,例如 jiangchang。"
|
||||
),
|
||||
)
|
||||
|
||||
key_hint = raw.lower()
|
||||
return PlatformResolveResult(
|
||||
error_code="PLATFORM_NOT_REGISTERED",
|
||||
message=(
|
||||
f"平台 {key_hint} 尚未注册,请先执行 "
|
||||
f"account platform ensure --key {key_hint} --display-name <名称> [--url <url>]"
|
||||
),
|
||||
)
|
||||
return resolve_platform_ref(input_name, conn)
|
||||
|
||||
|
||||
def get_platform_spec(platform_key: str, conn=None) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user