"""唯一允许交给 Android Intent 的商品链接。""" from __future__ import annotations from dataclasses import dataclass from urllib.parse import parse_qsl, urlsplit _SCHEME = "https" _HOST = "mobile.yangkeduo.com" _PATH = "/goods.html" class ProductUrlError(ValueError): """输入不是可安全重建的 canonical 商品链接。""" @dataclass(frozen=True) class ProductUrl: """经验证的商品标识及由它重建的 canonical URL。""" goods_id: str canonical_url: str def parse_product_url(value: str) -> ProductUrl: """只接受一个 ASCII 数字 ``goods_id`` 的拼多多商品直链。 解析结果绝不原样透传:Intent 使用的 URL 必须从 ``goods_id`` 重新构建,以排除 短链、额外参数、userinfo、fragment 和 URL 解析器的边缘表示。 """ if not isinstance(value, str): raise ProductUrlError("商品链接必须是字符串。") try: parsed = urlsplit(value) port = parsed.port query_pairs = parse_qsl(parsed.query, keep_blank_values=True, strict_parsing=True) except ValueError as error: raise ProductUrlError("商品链接格式无效。") from error if ( parsed.scheme != _SCHEME or parsed.hostname != _HOST or parsed.username is not None or parsed.password is not None or port is not None or parsed.path != _PATH or parsed.fragment ): raise ProductUrlError("商品链接不是允许的拼多多商品直链。") if len(query_pairs) != 1 or query_pairs[0][0] != "goods_id": raise ProductUrlError("商品链接必须且只能包含一个 goods_id 参数。") goods_id = query_pairs[0][1] if not goods_id or any(character < "0" or character > "9" for character in goods_id): raise ProductUrlError("goods_id 必须是纯数字。") canonical_url = f"{_SCHEME}://{_HOST}{_PATH}?goods_id={goods_id}" if value != canonical_url: raise ProductUrlError("商品链接必须使用唯一 canonical 表示。") return ProductUrl(goods_id=goods_id, canonical_url=canonical_url)