AutoRpa/rpa_cart/cart_yaoex.py

164 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1 号药城加入购物车。
from __future__ import annotations
import json
import time
from typing import Any
import requests
import urllib3
from cart_add import KIND_OK, KIND_SYSTEM, AddOutcome, classify_mall_message
from cart_match import CartOffer
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
GATEWAY = "https://gateway-b2b.fangkuaiyi.com"
YAOEX_ADD_URL = f"{GATEWAY}/api/cart/add"
# PC 搜索页加车写死的来源码,所有登录账号同一套。
YAOEX_ADD_TYPE = "1"
YAOEX_SOURCE_TYPE = 17052
YAOEX_FROMWHERE_CART = 0
# 按搜索结果的 spu 和仓,把数量加入 1 号药城购物车。缺 spu 或仓当系统失败。
def add_yaoex_cart(auth, offer: CartOffer, qty: int, *, session: requests.Session | None = None) -> AddOutcome:
ids = _ids(offer)
if not ids["spuCode"] or ids["supplyId"] in ("", None):
return AddOutcome(kind=KIND_SYSTEM, reason="缺少 sku")
common, payload = _form(auth, ids, qty)
own = session is None
client = session or requests.Session()
try:
response = client.post(
YAOEX_ADD_URL,
params=common,
data=payload,
headers=_headers(),
timeout=30,
verify=False,
)
response.raise_for_status()
return _outcome_from_json(response.json())
except Exception as exc:
return AddOutcome(kind=KIND_SYSTEM, reason=str(exc))
finally:
if own:
client.close()
# 从报价里取出加车要的 spu 和仓。仓优先 storeId / frontSellerCode,各账号字段名一样。
def _ids(offer: CartOffer) -> dict[str, Any]:
raw = offer.extra.get("raw") if isinstance(offer.extra.get("raw"), dict) else offer.extra
if not isinstance(raw, dict):
raw = {}
spu = str(raw.get("spuCode") or offer.sku_id or "").strip()
supply = _supply_id(raw)
return {"spuCode": spu, "supplyId": supply}
# 仓号收成网关要的值。纯数字就当整数,避免各账号字符串/数字不一致。
def _supply_id(raw: dict[str, Any]):
value = raw.get("storeId")
if value in (None, ""):
value = raw.get("frontSellerCode")
if value in (None, ""):
value = raw.get("supplyId")
if value in (None, ""):
value = raw.get("sellerCode")
if value in (None, ""):
return ""
text = str(value).strip()
if text.isdigit():
return int(text)
return text
# 拼 1 号药城加车表单:query 和 body 都带登录 token,itemList 是页面同款 JSON。
def _form(auth, ids: dict[str, Any], qty: int) -> tuple[dict[str, Any], dict[str, Any]]:
common = {
"traderName": "yaoex_pc",
"trader": "pc",
"closesignature": "yes",
"signature_method": "md5",
"signature": "****",
"timestamp": str(int(time.time() * 1000)),
"token": auth.token,
"userToken": auth.token,
"userId": getattr(auth, "user_id", ""),
"buyerCode": getattr(auth, "buyer_code", ""),
}
item: dict[str, Any] = {
"productNum": int(qty),
"spuCode": ids["spuCode"],
"supplyId": ids["supplyId"],
}
payload = {
**common,
"addType": YAOEX_ADD_TYPE,
"sourceType": YAOEX_SOURCE_TYPE,
"fromwhere": YAOEX_FROMWHERE_CART,
"itemList": json.dumps([item], ensure_ascii=False),
}
return common, payload
# 1 号药城加车请求头。来源页是搜索加车页,所有账号同一套。
def _headers() -> dict[str, str]:
return {
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"X-Request-Agent": "Axios",
"traderName": "yaoex_pc",
"trader": "pc",
"Origin": "https://mall.yaoex.com",
"Referer": "https://mall.yaoex.com/v2/pages/",
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36"
),
"Accept": "application/json, text/plain, */*",
}
# 把加车回包收成成功、业务失败或系统失败。看不懂的回包当系统失败。
def _outcome_from_json(payload: object) -> AddOutcome:
if not isinstance(payload, dict):
return AddOutcome(kind=KIND_SYSTEM, reason="加购返回异常")
if payload.get("success") is True or payload.get("code") in (0, "0", 200, "200"):
return AddOutcome(kind=KIND_OK)
rtn = payload.get("rtn_code")
if rtn not in (None, 0, "0"):
msg = str(payload.get("rtn_msg") or payload.get("rtn_tip") or "加购失败")
if "登录" in msg:
return AddOutcome(kind=KIND_SYSTEM, reason=msg)
return classify_mall_message(msg)
data = payload.get("data")
if not isinstance(data, dict):
data = payload
blob = _result_text(payload, data)
status = data.get("statusCode")
if status in (0, "0") and not blob:
return AddOutcome(kind=KIND_OK)
if blob:
return classify_mall_message(blob)
if status in (0, "0"):
return AddOutcome(kind=KIND_OK)
if status in (2, "2"):
return AddOutcome(kind=KIND_BUSINESS, reason="限购")
return AddOutcome(kind=KIND_SYSTEM, reason="加购失败")
# 从 data.message 和 cartResponseList[].message 拼出商城回文。
def _result_text(payload: dict, data: dict) -> str:
parts: list[str] = []
for row in data.get("cartResponseList") or data.get("addCartItemList") or []:
if isinstance(row, dict) and row.get("message"):
parts.append(str(row["message"]))
for key in ("message", "msg", "errorMsg"):
value = data.get(key) or payload.get(key)
if value:
parts.append(str(value))
return " ".join(parts).strip()