feat(jd): 添加京东相关路由及苹果权益充值功能
- 新增jd模块基础路由,整合app_store和payment子路由 - 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道 - 实现卡号密码查询接口,支持不同类别订单查询 - 新增短信认证相关接口,实现短信验证码发送及短信登录 - 新增商品管理接口,支持SKU详情查询及账号类下单功能 - 新增订单管理接口,实现订单删除功能 - 实现支付相关接口,增加刷新支付参数功能 - 定义完整请求及响应数据模型,确保接口数据规范 - 编写AppStoreSpider类,封装苹果应用内订单处理逻辑 - 引入多种代理池及请求重试机制,增强接口稳定性 - 添加详细日志记录,便于请求追踪与错误排查
This commit is contained in:
11
apps/jd/router/__init__.py
Normal file
11
apps/jd/router/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
|
||||
def router() -> APIRouter:
|
||||
router = APIRouter(prefix="/jd-anti-risk-spider")
|
||||
|
||||
from apps.jd.router import app_store, payment
|
||||
|
||||
router.include_router(app_store.router)
|
||||
router.include_router(payment.router)
|
||||
return router
|
||||
216
apps/jd/router/app_store.py
Normal file
216
apps/jd/router/app_store.py
Normal file
@@ -0,0 +1,216 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
import traceback
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
from apps.jd.schemas.models import (
|
||||
AppStoreRequest,
|
||||
AppleStoreRequestCategoryEnum,
|
||||
PlatPayResponseData,
|
||||
QueryCardRequest,
|
||||
QueryCardResponseData,
|
||||
)
|
||||
from apps.jd.services.app_store import AppStoreSpider
|
||||
from apps.jd.services.ctrip import XiechengCardSpider
|
||||
from apps.shared.proxy_pool.proxy_pool import ProxyPoolFactory
|
||||
from core.config import ProxyPoolType
|
||||
from core.responses import ApiResponse, BusinessCode, error, success
|
||||
from observability.logging import LoggerAdapter, get_logger_with_trace
|
||||
|
||||
router = APIRouter(prefix="/jd", tags=["苹果权益充值"])
|
||||
logger: LoggerAdapter = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
@router.post("/app/store", response_model=ApiResponse[PlatPayResponseData])
|
||||
async def app_store(
|
||||
request_data: AppStoreRequest,
|
||||
) -> ApiResponse[PlatPayResponseData] | ApiResponse[Any]:
|
||||
"""苹果权益充值"""
|
||||
# 接收参数
|
||||
order_num = request_data.order_num
|
||||
cookies = request_data.cookies.strip()
|
||||
res = {}
|
||||
|
||||
try:
|
||||
match request_data.category:
|
||||
case AppleStoreRequestCategoryEnum.Apple:
|
||||
return error(code=BusinessCode.NOT_IMPLEMENTED)
|
||||
# app_store_ = AppStoreSpider(
|
||||
# cookies=cookies,
|
||||
# face_price=request_data.face_price,
|
||||
# order_num=order_num,
|
||||
# )
|
||||
# code, res, msg = await run_in_threadpool(app_store_.run)
|
||||
# logger.info(msg=f"订单ID:{order_num},最终返回日志:{res}")
|
||||
# return success(data=res, message=msg)
|
||||
case AppleStoreRequestCategoryEnum.CTrip:
|
||||
# 100:10164405229636
|
||||
# 200:10164405699325
|
||||
# 300:10164405273963
|
||||
# 500:10164404344795
|
||||
# 200:10155862962901
|
||||
# 300:10155867355510
|
||||
# 500:10155863307550
|
||||
# 1000:10155867606890
|
||||
# 1000:10164403489305
|
||||
# 1000:10157256668464
|
||||
# 500:10157256668463
|
||||
# 300:10157256668462
|
||||
# 200:10157256668461
|
||||
# 100:10157256668460
|
||||
|
||||
skus = {
|
||||
10.0: "10186761790338",
|
||||
20.0: "10157199199546",
|
||||
100.0: "10157256668460",
|
||||
200.0: "10155862962901",
|
||||
300.0: "10164405273963",
|
||||
500.0: "10164404344795",
|
||||
1000.0: "10155867606890",
|
||||
102.0: "10157256668460",
|
||||
308.0: "10155867355510",
|
||||
520: "10155863307550",
|
||||
1026: "10155867606890",
|
||||
}
|
||||
if request_data.face_price not in skus:
|
||||
return error(
|
||||
code=BusinessCode.JD_ORDER_FACE_PRICE_ERR,
|
||||
data=PlatPayResponseData(
|
||||
**{
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": "不支持的充值面额",
|
||||
"face_price": 0,
|
||||
}
|
||||
),
|
||||
message="不支持的充值金额",
|
||||
)
|
||||
try:
|
||||
c_trip_spider = XiechengCardSpider(
|
||||
cookies=cookies,
|
||||
order_num=order_num,
|
||||
sku_id=skus[request_data.face_price],
|
||||
)
|
||||
code, res = await run_in_threadpool(c_trip_spider.run)
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return success(data=res)
|
||||
return error(code=code, data=res, message="请求完成")
|
||||
except Exception as e:
|
||||
logger.error(f"请求失败:{traceback.format_exc()}")
|
||||
return error(
|
||||
code=BusinessCode.INTERNAL_ERROR,
|
||||
data={
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(e),
|
||||
},
|
||||
)
|
||||
case AppleStoreRequestCategoryEnum.Walmart:
|
||||
skus = {
|
||||
100.0: "10140177420168",
|
||||
200.0: "10140177579817",
|
||||
300.0: "10140177668026",
|
||||
500.0: "10140177784201",
|
||||
1000.0: "10140177806944",
|
||||
}
|
||||
if request_data.face_price not in skus:
|
||||
return error(
|
||||
code=BusinessCode.JD_ORDER_FACE_PRICE_ERR,
|
||||
data={},
|
||||
message="不支持的充值金额",
|
||||
)
|
||||
try:
|
||||
code = BusinessCode.INTERNAL_ERROR
|
||||
for i in range(3):
|
||||
c_trip_spider = XiechengCardSpider(
|
||||
cookies=cookies,
|
||||
order_num=order_num,
|
||||
sku_id=skus[request_data.face_price],
|
||||
)
|
||||
code, res = await run_in_threadpool(c_trip_spider.run)
|
||||
if code == BusinessCode.JD_ORDER_RISK_ERR:
|
||||
proxy_pool = ProxyPoolFactory.get_proxy_pool(
|
||||
ProxyPoolType.EXPIRING, expire_time=60
|
||||
)
|
||||
proxy = proxy_pool.get_proxy(order_id=order_num)
|
||||
if proxy:
|
||||
proxy_pool.remove_invalid_proxy(proxy)
|
||||
break
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return success(data=res)
|
||||
return error(code=code, data=res, message="请求完成")
|
||||
except Exception as e:
|
||||
logger.error(f"请求失败:{traceback.format_exc()}")
|
||||
return error(
|
||||
code=BusinessCode.INTERNAL_ERROR,
|
||||
data={
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(e),
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"请求失败:{traceback.format_exc()}")
|
||||
return error(
|
||||
code=BusinessCode.INTERNAL_ERROR,
|
||||
data={
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(e),
|
||||
},
|
||||
message="请求完成",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/query/card", response_model=ApiResponse[QueryCardResponseData])
|
||||
async def query_card(request_data: QueryCardRequest):
|
||||
"""查卡密"""
|
||||
cookies = request_data.cookies.strip()
|
||||
|
||||
# 打印参数日志
|
||||
logger.info(f"订单:{request_data}")
|
||||
|
||||
try:
|
||||
match request_data.category:
|
||||
case AppleStoreRequestCategoryEnum.Apple:
|
||||
app_store_ = AppStoreSpider(
|
||||
cookies=cookies, order_num=request_data.jd_order_id
|
||||
)
|
||||
code, res = await run_in_threadpool(
|
||||
app_store_.query_card, request_data.jd_order_id
|
||||
)
|
||||
logger.info(
|
||||
f"订单号:{request_data.order_id},订单ID:{request_data.jd_order_id},最终返回日志:{res}"
|
||||
)
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return success(data=res)
|
||||
return error(code=code, data=res, message="查询完成")
|
||||
case (
|
||||
AppleStoreRequestCategoryEnum.CTrip
|
||||
| AppleStoreRequestCategoryEnum.Walmart
|
||||
):
|
||||
c_trip_spider = XiechengCardSpider(
|
||||
cookies=cookies,
|
||||
order_num=request_data.jd_order_id,
|
||||
sku_id=request_data.order_id,
|
||||
)
|
||||
code, res = await run_in_threadpool(
|
||||
c_trip_spider.get_locdetails, request_data.jd_order_id
|
||||
)
|
||||
logger.info(
|
||||
f"订单号:{request_data.order_id},订单ID:{request_data.jd_order_id},最终返回日志:{res}"
|
||||
)
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return success(data=res)
|
||||
return error(code=code, data=res, message="查询完成")
|
||||
return error(code=BusinessCode.JD_ORDER_RISK_ERR, data=None, message="查询失败")
|
||||
except Exception as e:
|
||||
logger.error(f"查询卡密失败: {traceback.format_exc()}")
|
||||
return error(code=BusinessCode.INTERNAL_ERROR, data=None, message="查询异常")
|
||||
41
apps/jd/router/auth.py
Normal file
41
apps/jd/router/auth.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.jd.schemas.models import SmsCodeRequest, SmsLoginRequest
|
||||
from apps.jd.services.login import LoginSpider
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
router = APIRouter(prefix="/jd/sms", tags=["短信认证"])
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
@router.post("/code")
|
||||
async def get_code(request_data: SmsCodeRequest):
|
||||
"""发送短信验证码"""
|
||||
phone = request_data.phone
|
||||
|
||||
res = LoginSpider(phone_=phone).run_send_code()
|
||||
logger.info(f"发送验证码返回:{res}")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def sms_login(request_data: SmsLoginRequest):
|
||||
"""短信登录"""
|
||||
phone = request_data.phone
|
||||
ck = request_data.ck
|
||||
code = request_data.code
|
||||
s_token = request_data.s_token
|
||||
jd_risk_token_id = request_data.jd_risk_token_id
|
||||
rsa_modulus = request_data.rsa_modulus
|
||||
|
||||
res = LoginSpider(phone_=phone).run_get_ck(
|
||||
ck=ck,
|
||||
code=code,
|
||||
s_token=s_token,
|
||||
jd_risk_token_id=jd_risk_token_id,
|
||||
rsa_modulus=rsa_modulus,
|
||||
)
|
||||
logger.info(f"短信登录返回:{res}")
|
||||
|
||||
return res
|
||||
65
apps/jd/router/goods.py
Normal file
65
apps/jd/router/goods.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from logging import LoggerAdapter
|
||||
import traceback
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from apps.jd.schemas.models import GoodsStoreRequest, SkuRequest
|
||||
from apps.jd.services.game_area import GameArea
|
||||
from apps.jd.services.goods_apple_card import GoodsAppleCard
|
||||
from core.responses import BusinessCode
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
|
||||
router = APIRouter(prefix="/jd", tags=["商品管理"])
|
||||
logger: LoggerAdapter = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
@router.post("/sku")
|
||||
async def get_sku_details(request_data: SkuRequest):
|
||||
"""获取sku参数"""
|
||||
cookies = request_data.cookies.strip()
|
||||
sku_id = request_data.sku_id
|
||||
|
||||
details = GameArea.get_details(cookies, sku_id)
|
||||
data = details.get("result", {})
|
||||
item = {"code": 100 if data else 110, "data": data}
|
||||
return item
|
||||
|
||||
|
||||
@router.post("/goods/store")
|
||||
async def goods_store(request_data: GoodsStoreRequest):
|
||||
"""账号类下单"""
|
||||
# 参数
|
||||
face_price = request_data.face_price
|
||||
order_num = request_data.order_num
|
||||
cookies = request_data.cookies.strip()
|
||||
brand_id = request_data.brand_id
|
||||
sku_id = request_data.sku_id
|
||||
username = request_data.username
|
||||
game_srv = request_data.gamesrv
|
||||
game_area_ = request_data.gamearea
|
||||
recharge_type = request_data.recharge_type
|
||||
|
||||
# 打印参数日志
|
||||
logger.info(f"订单ID:{order_num},cookies:{cookies}")
|
||||
logger.info(f"订单ID:{order_num},card_pwd:{face_price}")
|
||||
|
||||
try:
|
||||
app_store__ = GoodsAppleCard(
|
||||
cookies=cookies,
|
||||
face_price=face_price,
|
||||
order_num=order_num,
|
||||
sku_id=sku_id,
|
||||
brand_id=brand_id,
|
||||
username=username,
|
||||
game_srv=game_srv,
|
||||
game_area=game_area_,
|
||||
recharge_type=recharge_type,
|
||||
)
|
||||
code, res = app_store__.run()
|
||||
logger.info(f"订单ID:{order_num},goods_store最终返回日志:{res}")
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return res
|
||||
except Exception as e:
|
||||
logger.error("请求失败" + traceback.format_exc(), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="请求失败")
|
||||
25
apps/jd/router/order.py
Normal file
25
apps/jd/router/order.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.jd.schemas.models import DeleteOrderRequest
|
||||
from apps.jd.services.delete import DeleteOrder
|
||||
from observability.logging import LoggerAdapter, get_logger_with_trace
|
||||
from core.responses import error
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/v1/jd", tags=["订单管理"])
|
||||
logger: LoggerAdapter = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
@router.post("/delete_order")
|
||||
async def delete_order(request_data: DeleteOrderRequest):
|
||||
"""删除订单"""
|
||||
# 接收参数
|
||||
cookie = request_data.cookie.strip()
|
||||
order_id = request_data.order_id
|
||||
|
||||
delete_res = DeleteOrder(cookie=cookie, order_id=order_id).run()
|
||||
if isinstance(delete_res.get("body"), bool):
|
||||
code = 2000
|
||||
else:
|
||||
code = 2001
|
||||
return error(code=code, data={}, message="请求成功") # type: ignore
|
||||
96
apps/jd/router/payment.py
Normal file
96
apps/jd/router/payment.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import traceback
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from apps.jd.schemas.models import (
|
||||
PlatPayRequest,
|
||||
PlatPayResponseData,
|
||||
RefreshPaymentRequest,
|
||||
)
|
||||
from apps.jd.services.app_store import AppStoreSpider
|
||||
from apps.jd.services.ctrip import XiechengCardSpider
|
||||
from core.responses import ApiResponse, BusinessCode, success, error
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
|
||||
router = APIRouter(prefix="/order", tags=["支付"])
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
# @router.post("/plat_pay_channel", response_model=ApiResponse)
|
||||
# async def plat_pay_channel(request_data: PlatPayRequest):
|
||||
# """获取微信app端支付参数"""
|
||||
# # 接收参数
|
||||
# order_id = request_data.order_id
|
||||
# jd_order_id = request_data.jd_order_id
|
||||
# face_price = float(request_data.face_price)
|
||||
# pay_id = request_data.pay_id
|
||||
# cookies = request_data.cookies.strip()
|
||||
|
||||
# # 打印日志
|
||||
# logger.info(
|
||||
# f"获取微信app端支付参数 cookie:{cookies} order_id:{order_id} face_price:{face_price} pay_id:{pay_id}"
|
||||
# )
|
||||
|
||||
# try:
|
||||
# apple_store_spider = AppStoreSpider(
|
||||
# cookies=cookies,
|
||||
# order_num=order_id,
|
||||
# face_price=face_price,
|
||||
# )
|
||||
# code, deeplink = await run_in_threadpool(
|
||||
# apple_store_spider.ios_pay, jd_order_id, pay_id
|
||||
# )
|
||||
# data = PlatPayResponseData(
|
||||
# deeplink=deeplink, order_id=order_id, pay_id=pay_id, face_price=face_price
|
||||
# )
|
||||
# return success(code=getattr(code, "value", code), data=data, msg="请求成功")
|
||||
# except Exception as e:
|
||||
# logger.error("请求失败" + traceback.format_exc(), exc_info=True)
|
||||
# raise HTTPException(status_code=500, detail="请求失败")
|
||||
|
||||
|
||||
@router.post("/refresh-payment", response_model=ApiResponse[PlatPayResponseData])
|
||||
async def refresh_payment(request_data: RefreshPaymentRequest):
|
||||
"""获取微信app端支付参数"""
|
||||
# 打印日志
|
||||
logger.info(
|
||||
f"获取微信app端支付参数 cookie:{request_data.cookies} order_id:{request_data.order_id} pay_id:{request_data.pay_id} user_order_id:{request_data.user_order_id}"
|
||||
)
|
||||
# data = {
|
||||
# "deeplink": "weixin://wap/pay?prepayid%3Dwx11141602253864f3a03c1c654daa820001&package=3012187903&noncestr=1760163364&sign=b8e94df5e28a10e0938d84e322161882",
|
||||
# "order_id": "339328433580",
|
||||
# "pay_id": "652e4edb825348e78f692c17ef1bc8b9",
|
||||
# "face_price": 0
|
||||
# }
|
||||
# return my_json(code=100, data=data, msg="请求完成")
|
||||
res = {}
|
||||
try:
|
||||
for i in range(3):
|
||||
c_trip_spider = XiechengCardSpider(
|
||||
cookies=request_data.cookies,
|
||||
order_num=request_data.user_order_id,
|
||||
sku_id="",
|
||||
)
|
||||
code, res = await run_in_threadpool(
|
||||
c_trip_spider.refresh_payment_url,
|
||||
request_data.pay_id,
|
||||
request_data.order_id,
|
||||
)
|
||||
if code == BusinessCode.JD_ORDER_RISK_ERR:
|
||||
continue
|
||||
if code == BusinessCode.SUCCESS:
|
||||
return success(data=res)
|
||||
return error(
|
||||
code=BusinessCode.JD_ORDER_RISK_ERR,
|
||||
data=PlatPayResponseData(**{
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": res.get("remark"),
|
||||
}),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"请求失败:{traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail="请求失败")
|
||||
110
apps/jd/schemas/models.py
Normal file
110
apps/jd/schemas/models.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AppleStoreRequestCategoryEnum(Enum):
|
||||
Apple = "apple"
|
||||
CTrip = "cTrip"
|
||||
Walmart = "walmart"
|
||||
|
||||
|
||||
class AppStoreRequest(BaseModel):
|
||||
"""苹果权益充值请求"""
|
||||
|
||||
category: AppleStoreRequestCategoryEnum = Field(..., description="分类")
|
||||
face_price: float = Field(..., description="面值")
|
||||
order_num: str = Field(..., description="订单号")
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
|
||||
|
||||
class QueryCardRequest(BaseModel):
|
||||
"""查卡密请求"""
|
||||
|
||||
category: AppleStoreRequestCategoryEnum = Field(..., description="分类")
|
||||
order_id: str = Field(..., description="订单号")
|
||||
jd_order_id: str = Field(..., description="京东订单号")
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
|
||||
|
||||
class SkuRequest(BaseModel):
|
||||
"""获取SKU参数请求"""
|
||||
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
sku_id: str = Field(..., description="SKU ID")
|
||||
|
||||
|
||||
class GoodsStoreRequest(BaseModel):
|
||||
"""账号类下单请求"""
|
||||
|
||||
face_price: float = Field(..., description="面值")
|
||||
order_num: str = Field(..., description="订单号")
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
brand_id: str = Field(..., description="品牌ID")
|
||||
sku_id: str = Field(..., description="SKU ID")
|
||||
username: str | None = Field(None, description="用户名")
|
||||
gamesrv: str | None = Field(None, description="游戏服务器")
|
||||
gamearea: str | None = Field(None, description="游戏区域")
|
||||
recharge_type: int = Field(
|
||||
0, description="充值类型 1:username账号充值,2:游戏区服充值"
|
||||
)
|
||||
|
||||
|
||||
class PlatPayRequest(BaseModel):
|
||||
"""支付渠道请求"""
|
||||
|
||||
order_id: str = Field(..., description="订单ID")
|
||||
jd_order_id: str = Field(..., description="京东订单ID")
|
||||
face_price: float = Field(..., description="面值")
|
||||
pay_id: str = Field(..., description="支付ID")
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
|
||||
|
||||
class RefreshPaymentRequest(BaseModel):
|
||||
"""刷新支付请求"""
|
||||
|
||||
user_order_id: str = Field(..., description="用户订单ID")
|
||||
pay_id: str = Field(..., description="支付ID")
|
||||
cookies: str = Field(..., description="登录cookies")
|
||||
order_id: int = Field(..., description="订单ID")
|
||||
|
||||
|
||||
class DeleteOrderRequest(BaseModel):
|
||||
"""删除订单请求"""
|
||||
|
||||
cookie: str = Field(..., description="登录cookies")
|
||||
order_id: str = Field(..., description="订单ID")
|
||||
|
||||
|
||||
class SmsCodeRequest(BaseModel):
|
||||
"""发送短信验证码请求"""
|
||||
|
||||
phone: str = Field(..., description="手机号")
|
||||
|
||||
|
||||
class SmsLoginRequest(BaseModel):
|
||||
"""短信登录请求"""
|
||||
|
||||
phone: str = Field(..., description="手机号")
|
||||
ck: str = Field(..., description="cookies")
|
||||
code: str = Field(..., description="验证码")
|
||||
s_token: str = Field(..., description="s_token")
|
||||
jd_risk_token_id: str = Field(..., description="京东风控token ID")
|
||||
rsa_modulus: str = Field(..., description="RSA模数")
|
||||
|
||||
|
||||
class PlatPayResponseData(BaseModel):
|
||||
deeplink: str = Field("", description="微信唤起链接或支付信息")
|
||||
order_id: str = Field("", description="订单号")
|
||||
remark: str | None = Field(None, description="备注")
|
||||
pay_id: str = Field("", description="支付ID")
|
||||
face_price: float = Field(0.0, description="面值")
|
||||
|
||||
|
||||
class QueryCardResponseData(BaseModel):
|
||||
"""查卡密响应数据"""
|
||||
|
||||
order_status: str = Field(..., description="订单状态")
|
||||
card_num: str = Field(..., description="卡号")
|
||||
card_pwd: str = Field(..., description="卡密")
|
||||
remark: str = Field(default="", description="备注")
|
||||
773
apps/jd/services/app_store.py
Normal file
773
apps/jd/services/app_store.py
Normal file
@@ -0,0 +1,773 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from urllib import parse
|
||||
from curl_cffi import requests
|
||||
from fake_useragent import UserAgent
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from apps.jd.schemas.models import QueryCardResponseData
|
||||
from apps.jd.services.jstk import NormalJsTk, TxSMJsTk
|
||||
from apps.jd.services.utils import gen_cipher_ep, get_pay_sign, get_sign
|
||||
from apps.shared.proxy_pool.proxy_pool import ProxyPoolFactory
|
||||
from core.config import ProxyPoolType, settings
|
||||
from core.responses import BusinessCode
|
||||
from observability.logging import LoggerAdapter, get_logger_with_trace
|
||||
|
||||
#
|
||||
logger: LoggerAdapter = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class AppStoreSpider:
|
||||
def __init__(
|
||||
self,
|
||||
cookies,
|
||||
order_num,
|
||||
face_price: float = 0,
|
||||
user_client: str = UserAgent().random,
|
||||
):
|
||||
self.SKU_MA = {
|
||||
"10.00": 10022039398507,
|
||||
"50.00": 11170365589,
|
||||
"100.00": 11183343342,
|
||||
"200.00": 11183368356,
|
||||
"500.00": 11183445154,
|
||||
"1000.00": 10066407336810,
|
||||
"68.00": 10023403480808,
|
||||
"00.00": 10026503885976,
|
||||
}
|
||||
self.__proxy_pool = ProxyPoolFactory.get_proxy_pool(settings.proxy_type)
|
||||
self.order_num = order_num
|
||||
self.cookies = cookies
|
||||
self.face_price = face_price
|
||||
self.__user_client = user_client
|
||||
self.md5_key = "e7c398ffcb2d4824b4d0a703e38eb0bb"
|
||||
self.time_stamp = int(time.time() * 1000)
|
||||
self.submit_order_url = "https://api.m.jd.com/appstore/submitorder"
|
||||
self.ticket_url = "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha"
|
||||
self.jd_api = "https://api.m.jd.com/api"
|
||||
self.action_url = "https://api.m.jd.com/client.action"
|
||||
self.check_captcha_url = (
|
||||
"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha"
|
||||
)
|
||||
self.headers = {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"cookie": self.cookies,
|
||||
"origin": "https://txsm-m.jd.com",
|
||||
"referer": "https://txsm-m.jd.com/",
|
||||
"user-agent": self.get_user_agent(),
|
||||
}
|
||||
self.current_os = platform.system()
|
||||
self.js = None
|
||||
self.__js_tk = TxSMJsTk()
|
||||
self.__js_tk.generate_token(self.get_user_agent(), cookies)
|
||||
expiring_pool = ProxyPoolFactory.get_proxy_pool(
|
||||
ProxyPoolType.EXPIRING, expire_time=60
|
||||
)
|
||||
self.proxy = expiring_pool.get_proxy(order_id=uuid.uuid4().hex)
|
||||
# self.init_js()
|
||||
|
||||
def h5st(self, pt_pin: str, app_id: str, user_agent: str, data: dict):
|
||||
logger.info(f"h5st: {pt_pin}, {app_id}, {user_agent}, {data}")
|
||||
response = requests.post(
|
||||
"http://152.136.211.112:9001/h5st",
|
||||
json={
|
||||
"appCode": "Z6eXo8Dl",
|
||||
"pin": pt_pin,
|
||||
"ua": user_agent,
|
||||
"body": data,
|
||||
"appId": app_id,
|
||||
},
|
||||
)
|
||||
return response.json().get("body", {}).get("h5st", {}).get("h5st", "")
|
||||
|
||||
def get_user_agent(self) -> str:
|
||||
if "iPhone" in self.__user_client:
|
||||
return "Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1"
|
||||
return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/{random.randint(120, 140)}.0.0.0"
|
||||
|
||||
def get_eid_token(self):
|
||||
return self.__js_tk.get_token()
|
||||
|
||||
def format_number(self, number):
|
||||
if not number:
|
||||
number = 0
|
||||
number = float(number)
|
||||
formatted_number = "{:.2f}".format(number)
|
||||
return formatted_number
|
||||
|
||||
def get_s(self):
|
||||
use_bean = 0
|
||||
buy_num = 1
|
||||
face_price = self.face_price
|
||||
type = 1
|
||||
brand_id = 999440
|
||||
eid = self.__js_tk.get_eid()
|
||||
pay_mode = 0
|
||||
coupon_ids = ""
|
||||
sku_id = self.SKU_MA.setdefault(
|
||||
self.format_number(self.face_price), 10026503885976
|
||||
)
|
||||
total_price = self.format_number(self.face_price)
|
||||
order_source = 2
|
||||
order_source_type = 2
|
||||
s = f"{use_bean}{buy_num}{face_price}{type}{brand_id}{eid}{pay_mode}{coupon_ids}{sku_id}{total_price}{order_source}{order_source_type}"
|
||||
return s
|
||||
|
||||
def get_u(self):
|
||||
u = f"{self.time_stamp}{self.md5_key}"
|
||||
return u
|
||||
|
||||
def get_enc_str(self):
|
||||
s = self.get_s()
|
||||
u = self.get_u()
|
||||
_str = f"{s}{u}"
|
||||
enc_str = hashlib.md5(_str.encode()).hexdigest()
|
||||
return enc_str
|
||||
|
||||
def get_body(self, enc_str):
|
||||
item = {
|
||||
"useBean": 0,
|
||||
"buyNum": 1,
|
||||
"facePrice": self.face_price,
|
||||
"type": 1,
|
||||
"brandId": 999440,
|
||||
"eid": self.__js_tk.get_eid(),
|
||||
"payMode": "0",
|
||||
"couponIds": "",
|
||||
"skuId": self.SKU_MA.setdefault(
|
||||
self.format_number(self.face_price), 10026503885976
|
||||
),
|
||||
"totalPrice": self.format_number(self.face_price),
|
||||
"orderSource": 2,
|
||||
"orderSourceType": 2,
|
||||
"t": self.time_stamp,
|
||||
"channelSource": "txzs",
|
||||
"encStr": enc_str,
|
||||
"babelChannel": "ttt35",
|
||||
}
|
||||
json_string = json.dumps(item)
|
||||
encrypted_message = (
|
||||
base64.b64encode(json_string.encode("utf-8")).decode("utf-8").rstrip("=")
|
||||
)
|
||||
return encrypted_message
|
||||
|
||||
def submit_order(self, body):
|
||||
data = {
|
||||
"appid": "txsm-m",
|
||||
"client": "iPhone",
|
||||
"functionId": "appstore_order_submit_new",
|
||||
"uuid": uuid.uuid4().hex.replace("-", ""),
|
||||
"osVersion": "16.6",
|
||||
"screen": "1170.000046491623*2532.0001006126404",
|
||||
"t": self.time_stamp,
|
||||
"loginType": "2",
|
||||
"x-api-eid-token": self.__js_tk.get_eid(),
|
||||
"body": body,
|
||||
}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.post(
|
||||
url=self.submit_order_url,
|
||||
headers=self.headers,
|
||||
data=data,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
url=self.submit_order_url, headers=self.headers, data=data
|
||||
)
|
||||
|
||||
return response.json()
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=15)
|
||||
)
|
||||
def get_ticket_res(self):
|
||||
headers = {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Pragma": "no-cache",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": self.get_user_agent(),
|
||||
}
|
||||
url = "http://ticket_slide_server:99/api/TX?aid=2093769752&host=https://t.captcha.qq.com&ip="
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
verify=False,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.get(url, headers=headers, verify=False)
|
||||
return response.json()
|
||||
|
||||
def decrypt_card_info(self, message):
|
||||
key = "2E1ZMAF88CCE5EBE551FR3E9AA6FF322"
|
||||
card_info = self.js.call("decryptDes", message, key)
|
||||
card_info = json.loads(card_info)
|
||||
return card_info[0]
|
||||
|
||||
def get_card_res(self, order_id: str):
|
||||
"""
|
||||
获取订单卡号/卡密详情
|
||||
|
||||
:param order_id: 订单号
|
||||
:return: 卡号/卡密详情
|
||||
"""
|
||||
user_agent = self.get_user_agent()
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://recharge.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://recharge.m.jd.com/",
|
||||
"user-agent": user_agent,
|
||||
"x-referer-page": "https://recharge.m.jd.com/orderDetail",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
token_service = NormalJsTk()
|
||||
token_service.generate_token(user_agent, self.cookies)
|
||||
data = {
|
||||
"appid": "tsw-m",
|
||||
"functionId": "getGPOrderDetail",
|
||||
"t": self.time_stamp,
|
||||
"body": '{"appKey":"android","source":41,"orderId":"%s","version":"1.10","rechargeversion":"12.8","moduleName":"JDReactVirtualRecharge","apiVersion":"new"}'
|
||||
% (order_id),
|
||||
"uuid": "1736139761886772644158",
|
||||
"screen": "2560.5*1600.5",
|
||||
"x-api-eid-token": token_service.get_token(),
|
||||
}
|
||||
cookie_dict: dict[str, str] = {
|
||||
cookie.split("=")[0].strip(): cookie.split("=")[1].strip()
|
||||
for cookie in self.cookies.split(";")
|
||||
}
|
||||
h5st = self.h5st(cookie_dict.get("pt_pin", ""), "8e94a", user_agent, data)
|
||||
data["h5st"] = h5st
|
||||
response = requests.post(self.jd_api, headers=headers, data=data)
|
||||
return response.json()
|
||||
|
||||
def get_pay_res(self, order_id):
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
"origin": "https://trade.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://trade.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.get_user_agent(),
|
||||
"x-referer-page": "https://trade.m.jd.com/order/orderlist_jdm.shtml",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
params = {
|
||||
"t": f"{self.time_stamp}",
|
||||
"loginType": "2",
|
||||
"loginWQBiz": "golden-trade",
|
||||
"appid": "m_core",
|
||||
"client": "MacIntel",
|
||||
"clientVersion": "",
|
||||
"build": "",
|
||||
"osVersion": "null",
|
||||
"screen": "1920*1080",
|
||||
"networkType": "4g",
|
||||
"partner": "",
|
||||
"forcebot": "",
|
||||
"d_brand": "",
|
||||
"d_model": "",
|
||||
"lang": "zh-CN",
|
||||
"scope": "",
|
||||
"sdkVersion": "",
|
||||
"openudid": "",
|
||||
"uuid": uuid.uuid4().hex.replace("-", ""),
|
||||
"x-api-eid-token": self.__js_tk.get_token(),
|
||||
"functionId": "pay_info_m",
|
||||
"body": '{"appType":3,"bizType":"2","deviceUUId":"","platform":3,"sceneval":"2","source":"m_inner_myJd.orderFloor_orderlist","systemBaseInfo":"{\\"pixelRatio\\":2,\\"screenWidth\\":1920,\\"screenHeight\\":1080,\\"windowWidth\\":1920,\\"windowHeight\\":959,\\"statusBarHeight\\":null,\\"safeArea\\":{\\"bottom\\":0,\\"height\\":0,\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"width\\":0},\\"bluetoothEnabled\\":false,\\"locationEnabled\\":false,\\"wifiEnabled\\":false,\\"deviceOrientation\\":\\"landscape\\",\\"benchmarkLevel\\":-1,\\"brand\\":\\"\\",\\"model\\":\\"\\",\\"system\\":null,\\"platform\\":\\"MacIntel\\",\\"SDKVersion\\":\\"\\",\\"enableDebug\\":false,\\"language\\":\\"zh-CN\\",\\"version\\":\\"\\",\\"theme\\":\\"light\\",\\"fontSizeSetting\\":null,\\"albumAuthorized\\":false,\\"cameraAuthorized\\":false,\\"locationAuthorized\\":false,\\"microphoneAuthorized\\":false,\\"notificationAuthorized\\":false,\\"notificationAlertAuthorized\\":false,\\"notificationBadgeAuthorized\\":false,\\"notificationSoundAuthorized\\":false,\\"phoneCalendarAuthorized\\":false,\\"locationReducedAccuracy\\":false,\\"environment\\":\\"\\"}","orderId":"%s","origin":10,"tenantCode":"jgm","bizModelCode":"2","bizModeClientType":"M","bizModeFramework":"Taro","externalLoginType":1,"token":"3852b12f8c4d869b7ed3e2b3c68c9436","appId":"m91d27dbf599dff74"}'
|
||||
% order_id,
|
||||
"h5st": "20250513003554680%3Bg9gwwg93pzmtgwp3%3B9b070%3Btk03wac841c0518nh3UsKG1ttEEV8mnlrHkEhynQ-S2x3PbyeW6t_Cn0S29bQlTaYFu10XQnqBTZqpZVg7yWDGL1-Ien%3B57ceff3efad640ff671169173de4393cea87add0aeafeb28fe45d8ed0885f34d%3B4.2%3B1747067754680%3Be2dbf31f18c0566b03779cdfb7daf1889536ac5980ca54061cc5d12b276f6fcb74aa49e97fb21499ccffae2bb79d16ba8b664ce42ae53fce6b12c709789cf1eeb1e039f7a491fa6c0bb41380e593285cdb5cbaca0dd43586ff7937f44c9f6f0a1104467ba19dc3d8a5081e3bf7da385fee7ad469c515d6cd459c9efc82eb331e899e6c338ec36e3cfcf4466efd3d4bb31f752f3730e1343447f0524cfabc0dd22d9dbc853d384e6f6969ac7eb9339b72b5ff17147816fa422a79b375f28edbb490089ee8dd463b06339ba1536ba3e4ad90d82913ad4bd6049ff583666f24d648bb47c6d2f65380c4bda6ac0eea24fc622fa95763f9352247d21980f0b8dcdc54d7a2d18b84dad43660f88fcfb8c8613d86daa87f58addf1a60f3df6e5626753bf497864d03408578ff78bd5007b1bf86f0747b05681c58b32d27ae7b2de07059",
|
||||
}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.get(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.get(self.action_url, headers=headers, params=params)
|
||||
return response.json()
|
||||
|
||||
def plat_pay_channel_res(self, pay_id):
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://mpay.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://mpay.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.get_user_agent(),
|
||||
"x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
params = {"functionId": "platPayChannel", "appid": "mcashier", "scval": "mpay"}
|
||||
data = {
|
||||
"body": '{"appId":"m_D1vmUq63","payId":"%s","source":"mcashier","origin":"h5","mcashierTraceId":1729842386189}'
|
||||
% (pay_id),
|
||||
"x-api-eid-token": self.__js_tk.get_token(),
|
||||
"h5st": "20241025154626341;0587023779148689;303a7;tk03w7da11b8b18nOQ6HZGNLj6DdtLQBS695YHMu7RyONolcwWCRc8ihMUs5ITCem6HIGhdYo_DpJ62yYLkdrxIBxE0N;ab8a935407baae929b0d3e267f67693f9c00a5d876ad149c10e904371f87726f;3.1;1729842386341;24c9ee85e67cf80746dd82817ecbeafc7a829b35c7f446a4c7d476cc9faa1d8834a93323ad7bce9bef1bba682b93d2e3694e425ff68d304875c1ae9e2ae398cfd94e4ff03cd3bdd9f0f600a0d75c92d537baaa944d39072a92db7dc20c99e7f80889e289e78a1f8f93c57f8471890c464b78b61e9b3bbffea712e6d6c671ad12",
|
||||
}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.post(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=data,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
self.action_url, headers=headers, params=params, data=data
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def plat_wx_pay_res(self, pay_id):
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://mpay.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://mpay.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.get_user_agent(),
|
||||
"x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
params = {"functionId": "platWapWXPay", "appid": "mcashier", "scval": "mpay"}
|
||||
data = {
|
||||
"body": '{"appId":"m_D1vmUq63","payId":"%s","eid":"PBZHV4O4RF5SAA7QGYPZEPRPAYOCCF3WTUQYMWEFASLCJNYX2HWO7C35L5TYQUL66FGXVVMXDWKTBEEE24LW42XEWM","source":"mcashier","origin":"h5","mcashierTraceId":1729837716957}'
|
||||
% pay_id,
|
||||
"x-api-eid-token": "jdd03PBZHV4O4RF5SAA7QGYPZEPRPAYOCCF3WTUQYMWEFASLCJNYX2HWO7C35L5TYQUL66FGXVVMXDWKTBEEE24LW42XEWMAAAAMSYJMLE3IAAAAAC5OTMMGGUM5SYIX",
|
||||
# "h5st": "20241025142845015;0587023779148689;303a7;tk03w7da11b8b18nOQ6HZGNLj6DdtLQBS695YHMu7RyONolcwWCRc8ihMUs5ITCem6HIGhdYo_DpJ62yYLkdrxIBxE0N;48aee025d65fab059f98c546832f7a1d7bca99dba6bc0df9bd6328b9817cf394;3.1;1729837725015;24c9ee85e67cf80746dd82817ecbeafc7a829b35c7f446a4c7d476cc9faa1d8834a93323ad7bce9bef1bba682b93d2e3694e425ff68d304875c1ae9e2ae398cfd94e4ff03cd3bdd9f0f600a0d75c92d537baaa944d39072a92db7dc20c99e7f80889e289e78a1f8f93c57f8471890c464b78b61e9b3bbffea712e6d6c671ad12"
|
||||
}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.post(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=data,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
self.action_url, headers=headers, params=params, data=data
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def get_deep_link_res(self, mweb_url):
|
||||
headers = {
|
||||
"Host": "wx.tenpay.com",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"User-Agent": self.get_user_agent(),
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"Accept": "*/*",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Referer": mweb_url,
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
}
|
||||
prepay_id = re.search(r"prepay_id=(wx\w+)", mweb_url).group(1)
|
||||
package = re.search(r"package=(\d+)", mweb_url).group(1)
|
||||
ticket_res = self.get_ticket_res()
|
||||
logger.info(f"订单号:{self.order_num},获取ticket返回:{ticket_res}")
|
||||
ticket = ticket_res["ticket"]
|
||||
randstr = ticket_res["randstr"]
|
||||
params = {
|
||||
"ticket": ticket,
|
||||
"randstr": randstr,
|
||||
"prepayid": prepay_id,
|
||||
"package": package,
|
||||
}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.get(
|
||||
self.check_captcha_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.get(
|
||||
self.check_captcha_url, headers=headers, params=params
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def get_card_secret(self, jd_order_num):
|
||||
card_res = self.get_card_res(jd_order_num)
|
||||
logger.info(f"获取卡密信息返回:{card_res}")
|
||||
if not card_res:
|
||||
return 110, card_res
|
||||
if card_res.get("code") != "0":
|
||||
return 110, card_res
|
||||
card_info = card_res.get("result").get("cardInfos")
|
||||
if not card_info:
|
||||
return 110, card_res
|
||||
card_info = self.decrypt_card_info(card_info)
|
||||
return 100, card_info
|
||||
|
||||
def query_card(self, jd_order_num):
|
||||
"""
|
||||
查询订单状态和卡密信息
|
||||
|
||||
:param jd_order_num: 订单号
|
||||
:return: code=100成功,包含order_status, card_num, card_pwd字段
|
||||
"""
|
||||
try:
|
||||
card_res = self.get_card_res(jd_order_num)
|
||||
logger.info(f"获取卡密信息返回:{card_res}")
|
||||
|
||||
if not card_res:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="", card_num="", card_pwd="", remark=str(card_res)
|
||||
)
|
||||
# 判断没有登录的场景
|
||||
if card_res.get("code") == "20001":
|
||||
return BusinessCode.JD_ORDER_CK_ERR, QueryCardResponseData(
|
||||
order_status="", card_num="", card_pwd="", remark=str(card_res)
|
||||
)
|
||||
if card_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="", card_num="", card_pwd="", remark=str(card_res)
|
||||
)
|
||||
|
||||
card_info_list = card_res.get("result", {}).get("cardInfos", [])
|
||||
if not card_info_list:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="获取失败",
|
||||
card_num="",
|
||||
card_pwd="",
|
||||
remark=str(card_res),
|
||||
)
|
||||
|
||||
try:
|
||||
card_info = self.decrypt_card_info(card_info_list)
|
||||
return BusinessCode.SUCCESS, QueryCardResponseData(
|
||||
order_status=card_info.get("status", "成功"),
|
||||
card_num=card_info.get("cardNo", ""),
|
||||
card_pwd=card_info.get("cardPwd", ""),
|
||||
remark=str(card_res),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"解密卡密信息失败: {e}")
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="解密失败", card_num="", card_pwd=""
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"查询卡密信息异常: {e}")
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="查询失败", card_num="", card_pwd=""
|
||||
)
|
||||
|
||||
def run(self) -> tuple[BusinessCode, dict, str]:
|
||||
# 获取加密参数
|
||||
try:
|
||||
enc_str = self.get_enc_str()
|
||||
except KeyError as e:
|
||||
return BusinessCode.JD_ORDER_FACE_PRICE_ERR, {}, "充值面值有误"
|
||||
# 获取请求body
|
||||
body = self.get_body(enc_str)
|
||||
# 提交预付款订单
|
||||
order_res = self.submit_order(body)
|
||||
# 提交预付款订单
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},app_store提交预付款订单返回:{order_res}"
|
||||
)
|
||||
if order_res.get("code") == 300:
|
||||
return BusinessCode.JD_ORDER_CK_ERR, {}, str(order_res)
|
||||
if order_res.get("code") != 200:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(order_res)
|
||||
|
||||
# 提交预付款订单
|
||||
# # 获取支付信息
|
||||
pay_res = self.get_pay_res(order_res["data"])
|
||||
logger.info(f"订单号:{self.order_num},app_store获取支付信息返回:{pay_res}")
|
||||
# pay_res = {'body': {'payId': '0d02ea36cf7946a89ed6f51898d2d2a3', 'url': 'https://mpay.m.jd.com/mpay.25f73d1adb414ba7e0e3.html?appId=m_D1vmUq63&payId=0d02ea36cf7946a89ed6f51898d2d2a3&orderId=314518542769&tId=mpay'}, 'code': '0', 'message': 'success', 'timestamp': 1747127287841}
|
||||
if pay_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(pay_res)
|
||||
|
||||
# 获取支付信息
|
||||
pay_id = pay_res["body"]["payId"]
|
||||
# 获取微信支付信息
|
||||
pay_channel_res = self.plat_pay_channel_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},app_store请求微信渠道返回:{pay_channel_res}"
|
||||
)
|
||||
if pay_channel_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(pay_channel_res)
|
||||
|
||||
if "iPhone" in self.get_user_agent():
|
||||
code, msg = self.ios_pay(pay_channel_res["orderId"], pay_id)
|
||||
data = {}
|
||||
else:
|
||||
code, data, msg = self.web_pay(pay_id)
|
||||
if code != BusinessCode.SUCCESS:
|
||||
return code, data, msg
|
||||
|
||||
return (
|
||||
code,
|
||||
{
|
||||
"deeplink": msg,
|
||||
"order_id": pay_channel_res["orderId"],
|
||||
"pay_id": pay_id,
|
||||
"face_price": self.face_price,
|
||||
},
|
||||
"请求成功",
|
||||
)
|
||||
|
||||
def web_pay(self, pay_id: str) -> tuple[BusinessCode, dict, str]:
|
||||
# 获取微信支付信息
|
||||
wx_pay_res = self.plat_wx_pay_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},app_store获取微信支付信息返回:{wx_pay_res}"
|
||||
)
|
||||
if wx_pay_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(wx_pay_res)
|
||||
if wx_pay_res.get("errorCode") == "-1":
|
||||
wx_pay_res["order_id"] = self.order_num
|
||||
wx_pay_res["pay_id"] = pay_id
|
||||
wx_pay_res["face_price"] = self.face_price
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(wx_pay_res)
|
||||
mweb_url = wx_pay_res["payInfo"]["mweb_url"]
|
||||
# 获取支付链接信息
|
||||
deep_link_res = self.get_deep_link_res(mweb_url)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},app_store获取支付链接deep_link信息返回:{deep_link_res}"
|
||||
)
|
||||
if deep_link_res.get("retcode") != 1:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(deep_link_res)
|
||||
return BusinessCode.SUCCESS, {}, deep_link_res["deeplink"]
|
||||
|
||||
def ios_pay(self, order_id: str, pay_id: str) -> tuple[BusinessCode, str]:
|
||||
headers = {
|
||||
"Host": "api.m.jd.com",
|
||||
"charset": "UTF-8",
|
||||
"user-agent": "okhttp/3.12.1;jdmall;android;version/11.1.0;build/98139",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
url = "https://api.m.jd.com/client.action"
|
||||
func = "platPayChannel"
|
||||
version = "11.1.0"
|
||||
uuid_ = uuid.uuid4().hex.replace("-", "")
|
||||
ts = int(time.time() * 1000)
|
||||
ep = gen_cipher_ep(uuid_, ts)
|
||||
pay_sign = get_pay_sign(order_id=order_id, face_price=self.face_price)
|
||||
body = (
|
||||
'{"appId":"jd_android_app4","client":"android","fk_aid":"%s","fk_appId":"com.jingdong.app.mall","fk_latitude":"NfUaIOrNMes=","fk_longtitude":"NfUaIOrNMes=","fk_terminalType":"02","fk_traceIp":"26.26.26.1","hasCyberMoneyPay":"0","hasHuaweiPay":"0","hasOCPay":"0","hasUPPay":"0","orderId":"%s","orderPrice":"%s","orderType":"37","orderTypeCode":"0","origin":"native","payId":"%s","paySign":"%s","paySourceId":"2","payablePrice":"%s","sdkToken":"jdd016CSEHGQ3IPOGEXVOBUPDLTBSKZZUMESDDOP4PPRC2E2R7CC2K4LXQN63E6A3P3Y76GN4M5TMPIZGOWYQJG4MO6ET75VQFVQL2MIYRFQ01234567","source":"jdapp","style":"normal","supportNFC":"1"}'
|
||||
% (
|
||||
uuid_,
|
||||
order_id,
|
||||
self.face_price,
|
||||
pay_id,
|
||||
pay_sign,
|
||||
self.face_price,
|
||||
)
|
||||
)
|
||||
formatted_params = get_sign(func, body, uuid_, version)
|
||||
params = {
|
||||
"functionId": func,
|
||||
"clientVersion": version,
|
||||
"build": "98139",
|
||||
"client": "android",
|
||||
"partner": "wandoujia",
|
||||
# "eid": "eidAf760812200s2xOZPVA0sThGrvhABq1zrPMTmUOM1tv8FFg1FjE8yRJtYdV/UFhJuIkVZbrk/xl XPeoFQTNgMiYJpXeeyACCVPbt0/3R7R3Gd 8y",
|
||||
"sdkVersion": "28",
|
||||
"lang": "zh_CN",
|
||||
"harmonyOs": "0",
|
||||
"networkType": "wifi",
|
||||
# "uts": "0f31TVRjBSsqndu4/jgUPz6uymy50MQJ8fZr7wet7pLPYx9jEXMpd8VCD64sq/eBbsH5zZBXnKZMkN1vxnjOrpfx7GiQBINsuAELLpjOiZsCHkTDoRW/d9talOxyn2bo1YZLq8uq5Kdx/Fd7diA023Qwr+5V5TzeOnca3cc6QzMFh8p+DS2gR6Bimgz0BiNqbeN1q1NA9rJ/t5QOAIH9EA==",
|
||||
"uemps": "0-0",
|
||||
"ext": '{"prstate":"0","pvcStu":"1"}',
|
||||
"ef": "1",
|
||||
"ep": ep,
|
||||
"st": formatted_params["st"],
|
||||
"sign": formatted_params["sign"],
|
||||
"sv": formatted_params["sv"],
|
||||
}
|
||||
data = {"body": body, "": ""}
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.post(
|
||||
url=url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
data=data,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
)
|
||||
else:
|
||||
response = requests.post(url=url, params=params, headers=headers, data=data)
|
||||
client_res = response.json()
|
||||
logger.info(f"订单id:{self.order_num},获取支付参数res返回:{client_res}")
|
||||
# 不支持该支付类型
|
||||
if client_res.get("errorCode") == "-2":
|
||||
return BusinessCode.JD_ORDER_TYPE_NOT_SUPPORTED_ERR, "订单类型不支持"
|
||||
if client_res.get("errorCode") == "3":
|
||||
return BusinessCode.JD_ORDER_CK_ERR, "ck失效"
|
||||
# 订单失效
|
||||
if client_res.get("errorCode") == "-3":
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单失效"
|
||||
# 订单与下单账号不同
|
||||
if client_res.get("errorCode") == "-5":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, "订单与下单账号不匹配"
|
||||
# 订单已取消
|
||||
if client_res.get("errorCode") == "-100":
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消"
|
||||
# 订单已支付完成或已取消
|
||||
if client_res.get("mcashierConfirmInfo", {}).get("errorCode", "") == "-100":
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消"
|
||||
# 获取微信支付参数
|
||||
url = "https://api.m.jd.com/client.action"
|
||||
headers = {
|
||||
"Host": "api.m.jd.com",
|
||||
"charset": "UTF-8",
|
||||
"user-agent": self.get_user_agent(),
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
|
||||
uuid_ = uuid.uuid4().hex.replace("-", "")
|
||||
ts = int(time.time() * 1000)
|
||||
ep = gen_cipher_ep(uuid_, ts)
|
||||
pay_sign = get_pay_sign(order_id=order_id, face_price=self.face_price)
|
||||
sdk_token = "jdd01XYE6SS5ZXXG7F74OZC3PKG5LWF7W3GTVLTSUH56A2YAUXOT6NEEX4TRT3I3XDDYO7NQSIV4BSK3XGKWMROTBRKO4ENETDD4IEHFWZHA01234567"
|
||||
body = (
|
||||
'{"appId":"jd_android_app4","backUrl":"","client":"android","orderId":"%s","orderPrice":"%s","orderType":"37","orderTypeCode":"0","origin":"native","payId":"%s","paySign":"%s","sdkToken":"%s","source":"jdapp"}'
|
||||
% (order_id, self.face_price, pay_id, pay_sign, sdk_token)
|
||||
)
|
||||
func = "platWXPay"
|
||||
version = "11.0.0"
|
||||
formatted_params = get_sign(func, body, uuid_, version)
|
||||
params = {
|
||||
"functionId": "platWXPay",
|
||||
"clientVersion": "11.0.0",
|
||||
# "build": "97235",
|
||||
"client": "android",
|
||||
"partner": "huawei",
|
||||
# "eid": "eidAd2c08121b3s9eOeub59TR2G1LS3GtRpmFHjnr/5tBkXKUdbZOahaF5ejedAIDQFfwVV2GDYUzftxnEQRjdhD5bpPNR4B0qK9tPJ 5liDjiHAhDR3",
|
||||
"sdkVersion": "28",
|
||||
"lang": "zh_CN",
|
||||
"harmonyOs": "0",
|
||||
"networkType": "wifi",
|
||||
# "uts": "0f31TVRjBSsqndu4/jgUPz6uymy50MQJVJk5AARu8sQVESbyLWRZZNMf5XbN+023PgE2PL4I0aLSvISDbN3u1a1oIB1KTvzrqacX+46wkkSjXxvvYKogR9YCbfnMf2pdC5H/VDcJc2u6uHTxPVwvhLoJ8vX8cN45ZijAbikou9B5o2KTvMTzCfrSYgi3+mls/cA+6k+Ao5sFIIdtimZ6bw==",
|
||||
"uemps": "0-0",
|
||||
"ext": '{"prstate":"0","pvcStu":"1"}',
|
||||
"ef": "1",
|
||||
"ep": ep,
|
||||
"st": formatted_params["st"],
|
||||
"sign": formatted_params["sign"],
|
||||
"sv": formatted_params["sv"],
|
||||
}
|
||||
data = {
|
||||
"body": body,
|
||||
}
|
||||
pay_channel_res = None
|
||||
for i in range(3):
|
||||
try:
|
||||
proxy = self.__proxy_pool.get_proxy(order_id=self.order_num)
|
||||
if proxy:
|
||||
response = requests.post(
|
||||
url=url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
data=data,
|
||||
proxies={"http": proxy, "https": proxy},
|
||||
timeout=2,
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
url=url, params=params, headers=headers, data=data, timeout=2
|
||||
)
|
||||
pay_channel_res = response.json()
|
||||
logger.info(
|
||||
f"订单id:{self.order_num},获取微信支付参数返回:{pay_channel_res}"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
continue
|
||||
if not pay_channel_res:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, "爬虫异常"
|
||||
# 当前支付方式不可用,建议选择其他支付方式
|
||||
if pay_channel_res.get("errorCode") == "-1":
|
||||
return BusinessCode.INTERNAL_ERROR, "爬虫异常"
|
||||
# 加密算法失效
|
||||
if pay_channel_res.get("code") == "600":
|
||||
return BusinessCode.INTERNAL_ERROR, "加密算法异常"
|
||||
# 不支持该支付类型
|
||||
if pay_channel_res.get("errorCode") == "-2":
|
||||
return BusinessCode.JD_ORDER_TYPE_NOT_SUPPORTED_ERR, "订单类型不支持"
|
||||
# ck失效
|
||||
if pay_channel_res.get("errorCode") == "3":
|
||||
return BusinessCode.JD_ORDER_CK_ERR, "ck失效"
|
||||
# 订单失效
|
||||
if pay_channel_res.get("errorCode") == "-3":
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单失效"
|
||||
# 订单与下单账号不同
|
||||
if pay_channel_res.get("errorCode") == "-5":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, "订单与下单账号不匹配"
|
||||
# 订单已取消
|
||||
if pay_channel_res.get("errorCode") == "-100":
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消"
|
||||
# 订单已支付完成或已取消
|
||||
if (
|
||||
pay_channel_res.get("mcashierConfirmInfo", {}).get("errorCode", "")
|
||||
== "-100"
|
||||
):
|
||||
return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消"
|
||||
# 获取数据
|
||||
pay_info = pay_channel_res["payInfo"]
|
||||
# url转码
|
||||
wx_pay_info: str = (
|
||||
f"weixin://app/wxe75a2e68877315fb/pay/?nonceStr={pay_info['nonceStr']}&package={parse.quote(pay_info['package'])}&partnerId={pay_info['partnerId']}&prepayId={pay_info['prepayId']}&timeStamp={pay_info['timeStamp']}&sign={pay_info['sign']}×tamp={pay_info['timeStamp']}"
|
||||
)
|
||||
logger.info(f"获取微信app端支付参数返回:{wx_pay_info}")
|
||||
return BusinessCode.SUCCESS, wx_pay_info
|
||||
817
apps/jd/services/ctrip.py
Normal file
817
apps/jd/services/ctrip.py
Normal file
@@ -0,0 +1,817 @@
|
||||
import hashlib
|
||||
import json
|
||||
from logging import LoggerAdapter
|
||||
import platform
|
||||
import re
|
||||
import time
|
||||
|
||||
import fake_useragent
|
||||
from curl_cffi import ProxySpec, requests
|
||||
|
||||
from apps.jd.schemas.models import QueryCardResponseData
|
||||
from apps.shared.proxy_pool.proxy_pool import ProxyPoolFactory
|
||||
from core.config import ProxyPoolType
|
||||
from core.exceptions import JDServiceException
|
||||
from core.responses import BusinessCode
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
logger: LoggerAdapter = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class XiechengCardSpider:
|
||||
"""
|
||||
携程
|
||||
100元:10140177420168
|
||||
200元:10148161391225
|
||||
300元:10148163028307
|
||||
500元:10148178960836
|
||||
1000元:10148179392280
|
||||
"""
|
||||
|
||||
def __init__(self, cookies, order_num, sku_id):
|
||||
self.eid = None
|
||||
self.x_token = None
|
||||
self._session = requests.Session()
|
||||
self._order_num = order_num
|
||||
self.cookies = cookies
|
||||
self.time_stamp = int(time.time() * 1000)
|
||||
self.current_os = platform.system()
|
||||
self.sku_id = sku_id
|
||||
self.submit_order_url = "https://api.m.jd.com/appstore/submitorder"
|
||||
self.ticket_url = "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha"
|
||||
self.jd_api = "https://api.m.jd.com/api"
|
||||
self.action_url = "https://api.m.jd.com/client.action"
|
||||
self.check_captcha_url = (
|
||||
"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha"
|
||||
)
|
||||
self._expiring_pool = ProxyPoolFactory.get_proxy_pool(
|
||||
ProxyPoolType.EXPIRING, expire_time=60
|
||||
)
|
||||
self.user_agent = fake_useragent.FakeUserAgent().chrome
|
||||
|
||||
def _get_proxy(self):
|
||||
proxy = self._expiring_pool.get_proxy(order_id=self._order_num)
|
||||
return ProxySpec(all=proxy) if proxy else None
|
||||
|
||||
# def get_ticket_res(self):
|
||||
# for i in range(1):
|
||||
# try:
|
||||
# headers = {
|
||||
# "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
# "Accept-Language": "zh-CN,zh;q=0.9",
|
||||
# "Cache-Control": "no-cache",
|
||||
# "Connection": "keep-alive",
|
||||
# "Pragma": "no-cache",
|
||||
# "Upgrade-Insecure-Requests": "1",
|
||||
# "User-Agent": self.user_agent,
|
||||
# }
|
||||
# url = f"http://119.23.175.61:8877/captcha/tx?aid=2093769752&ip={self.proxy}&host=https://t.captcha.qq.com"
|
||||
# logger.info(f"获取ticket请求代理:{self.proxy}")
|
||||
# logger.info(f"获取ticket请求:{url}")
|
||||
# response = self._session.get(
|
||||
# url, headers=headers, verify=False, timeout=60
|
||||
# )
|
||||
# logger.info(f"获取ticket结果:{response.text}")
|
||||
# if response.json().get("msg_code") != 200:
|
||||
# continue
|
||||
# if response.json().get("result", {}).get(
|
||||
# "ticket"
|
||||
# ) and response.json().get("result", {}).get("randstr"):
|
||||
# return {
|
||||
# "ticket": response.json().get("result", {}).get("ticket"),
|
||||
# "randstr": response.json().get("result", {}).get("randstr"),
|
||||
# }
|
||||
# except Exception as e:
|
||||
# logger.error(e)
|
||||
# raise Exception("获取ticket失败")
|
||||
|
||||
def get_pay_res(self, order_id):
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
"origin": "https://trade.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://trade.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://trade.m.jd.com/order/orderlist_jdm.shtml",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
body = (
|
||||
'{"appType":3,"bizType":"2","deviceUUId":"","platform":3,"sceneval":"2","source":"m_inner_myJd.orderFloor_orderlist","systemBaseInfo":"{\\"pixelRatio\\":1.25,\\"screenWidth\\":2048,\\"screenHeight\\":1152,\\"windowWidth\\":414,\\"windowHeight\\":1034,\\"statusBarHeight\\":null,\\"safeArea\\":{\\"bottom\\":0,\\"height\\":0,\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"width\\":0},\\"bluetoothEnabled\\":false,\\"locationEnabled\\":false,\\"wifiEnabled\\":false,\\"deviceOrientation\\":\\"landscape\\",\\"benchmarkLevel\\":-1,\\"brand\\":\\"\\",\\"model\\":\\"\\",\\"system\\":null,\\"platform\\":\\"Win32\\",\\"SDKVersion\\":\\"\\",\\"enableDebug\\":false,\\"language\\":\\"zh-CN\\",\\"version\\":\\"\\",\\"theme\\":\\"light\\",\\"fontSizeSetting\\":null,\\"albumAuthorized\\":false,\\"cameraAuthorized\\":false,\\"locationAuthorized\\":false,\\"microphoneAuthorized\\":false,\\"notificationAuthorized\\":false,\\"notificationAlertAuthorized\\":false,\\"notificationBadgeAuthorized\\":false,\\"notificationSoundAuthorized\\":false,\\"phoneCalendarAuthorized\\":false,\\"locationReducedAccuracy\\":false,\\"environment\\":\\"\\"}","orderId":"%s","origin":10,"tenantCode":"jgm","bizModelCode":"2","bizModeClientType":"M","bizModeFramework":"Taro","externalLoginType":1,"token":"3852b12f8c4d869b7ed3e2b3c68c9436","appId":"m91d27dbf599dff74"}'
|
||||
% order_id
|
||||
)
|
||||
encrypt_body = self.sha256_hash(body)
|
||||
timestamp = f"{int(time.time() * 1000)}"
|
||||
object_id = "9b070"
|
||||
function_id = "pay_info_m"
|
||||
h5st = self.get_h5st(timestamp, encrypt_body, object_id, function_id)
|
||||
|
||||
params = {
|
||||
"t": f"{self.time_stamp}",
|
||||
"loginType": "2",
|
||||
"loginWQBiz": "golden-trade",
|
||||
"appid": "m_core",
|
||||
"client": "Win32",
|
||||
"clientVersion": "",
|
||||
"build": "",
|
||||
"osVersion": "null",
|
||||
"screen": "2048*1152",
|
||||
"networkType": "4g",
|
||||
"partner": "",
|
||||
"forcebot": "",
|
||||
"d_brand": "",
|
||||
"d_model": "",
|
||||
"lang": "zh-CN",
|
||||
"scope": "",
|
||||
"sdkVersion": "",
|
||||
"openudid": "",
|
||||
"uuid": "17295878824571442265187",
|
||||
"x-api-eid-token": self.x_token,
|
||||
"functionId": "pay_info_m",
|
||||
"body": body,
|
||||
"h5st": h5st,
|
||||
}
|
||||
proxy = self._get_proxy()
|
||||
response = self._session.get(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
verify=False,
|
||||
proxies=proxy,
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def plat_pay_channel_res(self, pay_id):
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://mpay.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://mpay.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
params = {"functionId": "platPayChannel", "appid": "mcashier", "scval": "mpay"}
|
||||
body = (
|
||||
'{"appId":"m_D1vmUq63","payId":"%s","source":"mcashier","origin":"h5","mcashierTraceId":1729842386189}'
|
||||
% pay_id
|
||||
)
|
||||
encrypt_body = self.sha256_hash(body)
|
||||
timestamp = f"{int(time.time() * 1000)}"
|
||||
object_id = "303a7"
|
||||
function_id = "plat_pay_channel"
|
||||
proxy = self._get_proxy()
|
||||
h5st = self.get_h5st(timestamp, encrypt_body, object_id, function_id)
|
||||
data = {"body": body, "x-api-eid-token": self.x_token, "h5st": h5st}
|
||||
response = self._session.post(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=data,
|
||||
verify=False,
|
||||
proxies=proxy,
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def get_locdetails(self, order_id):
|
||||
url = "https://api.m.jd.com/"
|
||||
data = {
|
||||
"appid": "loc",
|
||||
"loginType": "2",
|
||||
"cthr": "1",
|
||||
"loginWQBiz": "locdetails",
|
||||
"t": f"{int(time.time() * 1000)}",
|
||||
"functionId": "queryLocOrderDetail",
|
||||
"body": '{"version":"1.0.0","source":"","requestId":1757908327795,"orderId":"%s","oldHttp2Color":true}'
|
||||
% order_id,
|
||||
}
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://locdetails.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://locdetails.jd.com/",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://locdetails.jd.com/h5/index.html",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
# proxy = self._get_proxy()
|
||||
response = self._session.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
verify=False,
|
||||
# proxies={
|
||||
# "http": proxy,
|
||||
# "https": proxy,
|
||||
# },
|
||||
)
|
||||
res = response.json()
|
||||
if res.get("code") == 20001:
|
||||
return BusinessCode.JD_ORDER_CK_ERR, QueryCardResponseData(
|
||||
order_status="",
|
||||
card_num="",
|
||||
card_pwd="",
|
||||
remark=str(res),
|
||||
)
|
||||
|
||||
if res and res.get("code") == 0:
|
||||
logger.info(f"请求结果 {response.json()}")
|
||||
data = res.get("data", {})
|
||||
order_status_data = data.get("orderStatusData", {})
|
||||
shop_sku_info_list = data.get("shopSkuInfoList", [])
|
||||
|
||||
card_num = ""
|
||||
card_pwd = ""
|
||||
|
||||
if shop_sku_info_list and len(shop_sku_info_list) > 0:
|
||||
sku_infos = shop_sku_info_list[0].get("skuInfos", [])
|
||||
if sku_infos and len(sku_infos) > 0:
|
||||
code_info = sku_infos[0].get("codeInfo", [])
|
||||
if code_info and len(code_info) > 0:
|
||||
card_num = code_info[0].get("cardNum", "")
|
||||
card_pwd = code_info[0].get("pwdNum", "")
|
||||
|
||||
return BusinessCode.SUCCESS, QueryCardResponseData(
|
||||
order_status=order_status_data.get("statusName", ""),
|
||||
card_num=card_num,
|
||||
card_pwd=card_pwd,
|
||||
)
|
||||
else:
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData(
|
||||
order_status="", card_num="", card_pwd="", remark=str(res)
|
||||
)
|
||||
|
||||
def plat_wx_pay_res(self, pay_id):
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://mpay.m.jd.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://mpay.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
params = {"functionId": "platWapWXPay", "appid": "mcashier", "scval": "mpay"}
|
||||
body = (
|
||||
'{"appId":"m_D1vmUq63","payId":"%s","eid":"%s","source":"mcashier","origin":"h5","mcashierTraceId":1729837716957}'
|
||||
% (pay_id, self.eid)
|
||||
)
|
||||
encrypt_body = self.sha256_hash(body)
|
||||
timestamp = f"{int(time.time() * 1000)}"
|
||||
object_id = "303a7"
|
||||
function_id = "plat_wx_pay"
|
||||
h5st = self.get_h5st(timestamp, encrypt_body, object_id, function_id)
|
||||
data = {"body": body, "x-api-eid-token": self.x_token, "h5st": h5st}
|
||||
proxy = self._get_proxy()
|
||||
response = self._session.post(
|
||||
self.action_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=data,
|
||||
verify=False,
|
||||
proxies=proxy,
|
||||
)
|
||||
return response.json()
|
||||
|
||||
# def get_deep_link_res(self, mweb_url):
|
||||
# headers = {
|
||||
# "Host": "wx.tenpay.com",
|
||||
# "sec-ch-ua-platform": '"Windows"',
|
||||
# "User-Agent": self.user_agent,
|
||||
# "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
# "sec-ch-ua-mobile": "?0",
|
||||
# "Accept": "*/*",
|
||||
# "Sec-Fetch-Site": "same-origin",
|
||||
# "Sec-Fetch-Mode": "cors",
|
||||
# "Sec-Fetch-Dest": "empty",
|
||||
# "Referer": mweb_url,
|
||||
# "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
# }
|
||||
# prepay_id = re.search(r"prepay_id=(wx\w+)", mweb_url).group(1)
|
||||
# package = re.search(r"package=(\d+)", mweb_url).group(1)
|
||||
# ticket_res = self.get_ticket_res()
|
||||
# logger.info(f"订单号:{self._order_num},获取ticket返回:{ticket_res}")
|
||||
# ticket = ticket_res["ticket"]
|
||||
# randstr = ticket_res["randstr"]
|
||||
# params = {
|
||||
# "ticket": ticket,
|
||||
# "randstr": randstr,
|
||||
# "prepayid": prepay_id,
|
||||
# "package": package,
|
||||
# }
|
||||
# proxy = self._get_proxy()
|
||||
# response = self._session.get(
|
||||
# self.check_captcha_url,
|
||||
# headers=headers,
|
||||
# params=params,
|
||||
# verify=False,
|
||||
# proxies=proxy,
|
||||
# )
|
||||
# return response.json()
|
||||
|
||||
@staticmethod
|
||||
def sha256_hash(text):
|
||||
# 创建 SHA-256 哈希对象
|
||||
sha256 = hashlib.sha256()
|
||||
# 更新哈希对象(输入必须为字节类型)
|
||||
sha256.update(text.encode("utf-8"))
|
||||
# 返回 16 进制格式的哈希值
|
||||
return sha256.hexdigest()
|
||||
|
||||
def _get_pt_pin(self, cookie_str):
|
||||
cookies = {}
|
||||
for item in cookie_str.strip(";").split(";"):
|
||||
if "=" in item:
|
||||
key, value = item.strip().split("=", 1)
|
||||
cookies[key] = value
|
||||
|
||||
pt_pin = cookies.get("pt_pin")
|
||||
return pt_pin
|
||||
|
||||
def get_h5st(self, timestamp, body, object_id, function_id):
|
||||
sua = "Windows NT 10.0; Win64; x64"
|
||||
pt_pin = self._get_pt_pin(self.cookies)
|
||||
params_str = f"ai={object_id}&sua={sua}&pin={pt_pin}&appid=m_core&functionId={function_id}&body={body}&client=Win32&clientVersion=2.5.2&t={timestamp}"
|
||||
h5st = requests.get(f"http://127.0.0.1:8887/jd/h5st?{params_str}").text
|
||||
return h5st
|
||||
|
||||
def get_x_token(self):
|
||||
proxy = None
|
||||
_proxy = self._get_proxy()
|
||||
if _proxy:
|
||||
proxy = _proxy.get("all")
|
||||
|
||||
data = {"type": "1", "str": self.user_agent}
|
||||
if proxy:
|
||||
data["proxy"] = proxy
|
||||
|
||||
response = requests.post(
|
||||
"http://127.0.0.1:8887/api/stash/algorithm",
|
||||
data=data,
|
||||
)
|
||||
logger.info(f"获取x-api-eid-token返回:{response.text}")
|
||||
res = response.json()
|
||||
token = res["data"]["token"]
|
||||
eid = res["data"]["eid"]
|
||||
return token, eid
|
||||
|
||||
def get_current_order(self, retry_count: int = 3):
|
||||
if not retry_count:
|
||||
return JDServiceException(BusinessCode.JD_ORDER_RISK_ERR)
|
||||
body = (
|
||||
'{"deviceUUID":"6785593751540242498","appId":"wxae3e8056daea8727","appVersion":"2.5.2","tenantCode":"jgm","bizModelCode":"3","bizModeClientType":"M","token":"3852b12f8c4d869b7ed3e2b3c68c9436","externalLoginType":1,"referer":"https://item.m.jd.com/","resetGsd":true,"useBestCoupon":"1","locationId":"1-72-2819-0","packageStyle":true,"sceneval":"2","balanceCommonOrderForm":{"supportTransport":false,"action":1,"overseaMerge":false,"international":false,"netBuySourceType":0,"appVersion":"2.5.2","tradeShort":false},"balanceDeviceInfo":{"resolution":"2048*1152"},"cartParam":{"skuItem":{"skuId":"%s","num":"1","orderCashBack":false,"extFlag":{}}}}'
|
||||
% self.sku_id
|
||||
)
|
||||
encrypt_body = self.sha256_hash(body)
|
||||
timestamp = f"{int(time.time() * 1000)}"
|
||||
object_id = "bd265"
|
||||
function_id = "balance_getCurrentOrder_m"
|
||||
h5st = self.get_h5st(timestamp, encrypt_body, object_id, function_id)
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://trade.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://trade.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://trade.m.jd.com/pay",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
|
||||
url = "https://api.m.jd.com/client.action"
|
||||
data = {
|
||||
"t": timestamp,
|
||||
"body": body,
|
||||
"h5st": h5st,
|
||||
"scval": self.sku_id,
|
||||
"client": "Win32",
|
||||
"clientVersion": "2.5.2",
|
||||
"osVersion": "other",
|
||||
"screen": "2048*1152",
|
||||
"networkType": "false",
|
||||
"d_brand": "",
|
||||
"d_model": "",
|
||||
"lang": "zh-CN",
|
||||
"sdkVersion": "2.5.2",
|
||||
"appid": "m_core",
|
||||
"openudid": "",
|
||||
"x-api-eid-token": self.x_token,
|
||||
"functionId": "balance_getCurrentOrder_m",
|
||||
"uuid": "17573259367521124301157",
|
||||
"loginType": "2",
|
||||
"xAPIScval3": "unknown",
|
||||
}
|
||||
proxy = self._get_proxy()
|
||||
response = self._session.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
impersonate="chrome",
|
||||
verify=False,
|
||||
proxies=proxy,
|
||||
)
|
||||
if (
|
||||
response.ok
|
||||
and response.json()
|
||||
and response.json().get("body", {}).get("errorCode") == "601"
|
||||
):
|
||||
return self.get_current_order(retry_count - 1)
|
||||
|
||||
def submit_order(self):
|
||||
body = {
|
||||
"deviceUUID": "6279258698405299736",
|
||||
"appId": "wxae3e8056daea8727",
|
||||
"tenantCode": "jgm",
|
||||
"bizModelCode": "3",
|
||||
"bizModeClientType": "M",
|
||||
"token": "3852b12f8c4d869b7ed3e2b3c68c9436",
|
||||
"externalLoginType": 1,
|
||||
"appVersion": "2.5.2",
|
||||
"referer": "https://item.m.jd.com/",
|
||||
"checkPayPassport": False,
|
||||
"checkpwdV2": False,
|
||||
"isEncryptionMobile": True,
|
||||
"outStockVendorIdList": [12514479],
|
||||
"mainSkuIdList": [int(self.sku_id)],
|
||||
"balanceDataServerSkuVOList": [
|
||||
{
|
||||
"id": int(self.sku_id),
|
||||
"jdPrice": "499.00",
|
||||
"buyNum": 1,
|
||||
"firstCategoryId": 4938,
|
||||
"secondCategoryId": 11760,
|
||||
"thirdCategoryId": 22501,
|
||||
"promoId": 305236894242,
|
||||
"venderId": 12514479,
|
||||
"type": 1,
|
||||
}
|
||||
],
|
||||
"balanceTableWareVoList": [{}],
|
||||
"cashierDeskBackUrl": f"https://trade.m.jd.com/buy/done.shtml?dealId=%24%7BorderId%7D&sceneval=2&fromPay=1&ptag=7039.27.14&gift_skuid={self.sku_id}&gift_venderid=12514479&gift_cid=22501&normal=1",
|
||||
"payType": "4",
|
||||
"subPayType": "",
|
||||
"licenseList": [],
|
||||
"balanceCommonOrderForm": {
|
||||
"action": 1,
|
||||
"overseaMerge": False,
|
||||
"international": False,
|
||||
"netBuySourceType": 0,
|
||||
"appVersion": "2.5.2",
|
||||
"supportTransport": False,
|
||||
"tradeShort": False,
|
||||
"useChannelFlag": "10000000",
|
||||
"hasSingleOrderGovSubsidy": False,
|
||||
"unionPayCouponEffective": False,
|
||||
"oldAgeStyle": False,
|
||||
"balanceRefreshByAction": "1",
|
||||
"supportUserPrivacy": True,
|
||||
"userPrivacyChecked": True,
|
||||
},
|
||||
"balanceExt": {
|
||||
"baiDuJumpButtonSwitch": False,
|
||||
"bubbleTips": {"enable": True, "num": 1, "time": 3, "useNum": 0},
|
||||
"cashierDeskEnable": True,
|
||||
"cashierPayFlag": "0",
|
||||
"checkIdInfo": False,
|
||||
"couponRedText": "",
|
||||
"hasBackupStorage": False,
|
||||
"hasCwbg": False,
|
||||
"hasFreightInsurance": False,
|
||||
"isInternational": False,
|
||||
"isSupportTranport": False,
|
||||
"jdCombineType": 0,
|
||||
"knowledgeServiceStatus": 0,
|
||||
"noAddressMatchDegradeSwitch": True,
|
||||
"overseaMerge": False,
|
||||
"plusFloorStr": '{"addressType":0,"area":"19-1601-3633-63243","freightInfoList":[],"invoiceType":1,"payWay":4,"plusPromotionRequest":{"couponTotalAmount":0.00,"fareCouponTotalAmount":0.00,"officialDiscountTotalDiscount":0.00,"promotionReduceTotalAmount":0.00,"promotionTotalPrice":499.00,"redBagTotalAmount":0.00,"totalCashGiftDiscount":0,"totalCashGiftNew":0,"totalCashGiftOld":0,"totalParallelDiscount":0.00},"plusStatus":"203","requestConditionList":[105,106,305,529,523,521,522,351],"skuInfoRequestList":[{"col_type":"0","companyType":0,"factoryShip":0,"firstCategory":4938,"isCanUseDongCoupon":1,"isCanUseJingCoupon":1,"isJxzy":0,"isLoc":1,"isOverseaPurchase":0,"jdPrice":"499.00","number":"1","secondCategory":11760,"shopId":12204479,"skuExtension":{"fields":{"fare":"2849262","saler":"","saleAttributes":"[{\\"saleName\\":\\"颜色\\",\\"dim\\":1},{\\"saleName\\":\\"尺码\\",\\"dim\\":2}]","maxBuyNum":"19","is7ToReturn":"0","features":"consumptionVAT:0,inputVAT:0,outputVAT:0","vender_attribute":"","isHitGovSubsidy":"0","selectedGBCZGovConsumerCoupon":"false","yn":"1","vender_name":"易点生活电子商务有限公司","product_id":"10028314345641","sale_atts":"","warranty":"","timeliness_id":"0","sku_name":"【谨防刷单诈骗】沃尔玛大卖场卡500元 官方卡密 卡号8688 不支持山姆 本店不刷单 谨防诈骗 不支持退换","supply_unit":"","model":"","shopId":"12204479","cn_sku_name":"【谨防刷单诈骗】沃尔玛大卖场卡500元 官方卡密 卡号8688 不支持山姆 本店不刷单 谨防诈骗 不支持退换","height":"0","MN":"19","first_buyer_post":"4541","unLimit_cid":"22501","isCanVAT":"0","pay_first":"1","tsfw":"p8,","ms":"0","weight":"0","sku_id":"%s","tax":"consumptionVAT:0,inputVAT:0,outputVAT:0","shop_name":"沃尔玛礼品卡专卖店","product_name":"【谨防刷单诈骗】沃尔玛大卖场卡500元 官方卡密 卡号8688 不支持山姆","brand_id":"250870","shop_id":"12204479","size":"不支持退换","brandId":"250870","col_type":"0","color":"本店不刷单 谨防诈骗","cn_color":"本店不刷单 谨防诈骗","isLOC":"2","img_dfs_url":"jfs/t1/225028/24/16896/94540/6667fb55Fad484540/4069fd317318e9da.jpg","outer_id":"DSwemmck0500","platform":"1","vender_id":"12514479","jc_buyer":"","category_id":"22501","fxg":"0","isQdh":"0","venderAttribute":"","sku_tag":"0","category_id1":"4938","sku_mark":"0","template_type_attributes":"[{\\"attrid\\":\\"1001050620\\",\\"dim\\":1,\\"saleName\\":\\"颜色\\",\\"saleValue\\":\\"本店不刷单 谨防诈骗\\",\\"sequenceNo\\":1,\\"valueId\\":\\"2916524754\\"},{\\"attrid\\":\\"1001051741\\",\\"dim\\":2,\\"saleName\\":\\"尺码\\",\\"saleValue\\":\\"不支持退换\\",\\"sequenceNo\\":1,\\"valueId\\":\\"3924738280\\"}]","category_id2":"11760","sale_template_id":"POP_MODEL","jc_saler":"","day_limited_sales":"19","length":"0","locGroupId":"-100","vender_col_type":"0","sku_status":"1","allnum":"0","containsGovConsumerCoupon":"false","width":"0"},"fresh":false,"num":1,"parallelPromo":false,"samShop":false,"selfSupport":false,"shoppingMalls":false,"skuPriceAfterSinglePromotion":"499.00","skuUuid":"1012_F2t2t3H1499005262095200256","spuId":"-1","storeId":"-1","vendor":"12514479","vendorType":2},"skuId":%s,"skuMark":"","spuId":"-1","thirdCategory":22501,"uuid":"1012_F2t2t3H1499005262095200256","venderId":12514479,"vender_bizid":",popsop,","vender_col_type":"0","vendorType":0}],"totalPrice":"499.00"}'
|
||||
% (self.sku_id, self.sku_id),
|
||||
"selectedCouponNum": 0,
|
||||
"sellLargeDay": "7",
|
||||
"supportPaymentSkuList": [self.sku_id],
|
||||
"useBestCoupon": True,
|
||||
},
|
||||
"actualPayment": "499.00",
|
||||
"sendGift": {},
|
||||
"dsList": [
|
||||
{"paramName": "report_time", "paramVal": ""},
|
||||
{"paramName": "deal_id", "paramVal": ""},
|
||||
{"paramName": "buyer_uin"},
|
||||
{"paramName": "pin", "paramVal": ""},
|
||||
{
|
||||
"paramName": "cookie_pprd_p",
|
||||
"paramVal": "UUID.17512753463451844059233-LOGID.1751857899901.1014192400",
|
||||
},
|
||||
{
|
||||
"paramName": "cookie_pprd_s",
|
||||
"paramVal": "76161171.17512753463451844059233.1751275346.1751856681.1751857862.11",
|
||||
},
|
||||
{"paramName": "cookie_pprd_t"},
|
||||
{"paramName": "ip", "paramVal": ""},
|
||||
{"paramName": "visitkey", "paramVal": "6279258698405299736"},
|
||||
{"paramName": "gen_entrance", "paramVal": ""},
|
||||
{"paramName": "deal_src", "paramVal": "7"},
|
||||
{"paramName": "item_type", "paramVal": "1"},
|
||||
{"paramName": "fav_unixtime", "paramVal": ""},
|
||||
{"paramName": "pay_type", "paramVal": "0"},
|
||||
{"paramName": "ab_test", "paramVal": ""},
|
||||
{"paramName": "serilize_type", "paramVal": "0"},
|
||||
{"paramName": "property1", "paramVal": "0"},
|
||||
{"paramName": "property2", "paramVal": "0"},
|
||||
{"paramName": "property3", "paramVal": "0"},
|
||||
{"paramName": "property4", "paramVal": "0"},
|
||||
{"paramName": "seller_uin", "paramVal": "0"},
|
||||
{"paramName": "pp_item_id", "paramVal": ""},
|
||||
{"paramName": "openid"},
|
||||
{"paramName": "orderprice", "paramVal": ""},
|
||||
{"paramName": "actiontype", "paramVal": ""},
|
||||
{"paramName": "extinfo", "paramVal": ""},
|
||||
{"paramName": "ext1", "paramVal": self.sku_id},
|
||||
{"paramName": "ext2", "paramVal": ""},
|
||||
{"paramName": "ext3", "paramVal": ""},
|
||||
{"paramName": "ext4", "paramVal": ""},
|
||||
{"paramName": "ext5", "paramVal": ""},
|
||||
{"paramName": "ext6"},
|
||||
{"paramName": "ext7", "paramVal": ""},
|
||||
{"paramName": "ext8", "paramVal": "0"},
|
||||
{"paramName": "ext9", "paramVal": "0|0|0|0|0||0|0"},
|
||||
{"paramName": "ext10", "paramVal": "|||"},
|
||||
{
|
||||
"paramName": "ext11",
|
||||
"paramVal": "http://wq.jd.com/wxapp/pages/pay/index/index",
|
||||
},
|
||||
{"paramName": "ext12", "paramVal": "1"},
|
||||
{"paramName": "ext13", "paramVal": ""},
|
||||
{"paramName": "ext14", "paramVal": ""},
|
||||
{"paramName": "ext15", "paramVal": ""},
|
||||
{"paramName": "ext16", "paramVal": ""},
|
||||
{
|
||||
"paramName": "ext17",
|
||||
"paramVal": "76161171%7Ciosapp%7Ct_335139774%7Cappshare%7CCopyURL_shareidde4fa5e467a1fbe323b52ed0fa61777f5ce3e1fe17503293501852_shangxiang_none%7C1751857899902",
|
||||
},
|
||||
{"paramName": "ext18"},
|
||||
{"paramName": "ext19", "paramVal": ""},
|
||||
{"paramName": "ext20"},
|
||||
{
|
||||
"paramName": "fpa",
|
||||
"paramVal": "7f07f4e1-6ac2-2918-ff9a-18320538d8b2-1743129927",
|
||||
},
|
||||
{
|
||||
"paramName": "fpb",
|
||||
"paramVal": "BApXSBoHW4fJA8VrxmIXHrE1TcUW-_mloBgFTnghK9xJ1MvE2-4G2",
|
||||
},
|
||||
{"paramName": "ext21", "paramVal": ""},
|
||||
{"paramName": "ext22", "paramVal": ""},
|
||||
{"paramName": "ext23", "paramVal": "NULL"},
|
||||
{"paramName": "ext24", "paramVal": "NULL"},
|
||||
{"paramName": "ext25", "paramVal": "NULL"},
|
||||
{"paramName": "ext26", "paramVal": "NULL"},
|
||||
{"paramName": "ext27", "paramVal": "NULL"},
|
||||
{"paramName": "ext28", "paramVal": "NULL"},
|
||||
{"paramName": "ext29", "paramVal": "NULL"},
|
||||
{"paramName": "ext30", "paramVal": "NULL"},
|
||||
{"paramName": "ext31", "paramVal": "NULL"},
|
||||
{"paramName": "ext32", "paramVal": "NULL"},
|
||||
{"paramName": "ext33", "paramVal": "NULL"},
|
||||
{"paramName": "ext34", "paramVal": "NULL"},
|
||||
{"paramName": "ext35", "paramVal": "NULL"},
|
||||
{"paramName": "ext36", "paramVal": "NULL"},
|
||||
{"paramName": "ext37", "paramVal": "NULL"},
|
||||
{"paramName": "ext38", "paramVal": "NULL"},
|
||||
{"paramName": "dt", "paramVal": ""},
|
||||
],
|
||||
"govUseGisLocation": False,
|
||||
"packageStyle": True,
|
||||
"sceneval": "2",
|
||||
"balanceDeviceInfo": {"resolution": "2048*1152"},
|
||||
"balanceId": "5351641558031892481751857904612",
|
||||
}
|
||||
encrypt_body = self.sha256_hash(json.dumps(body))
|
||||
timestamp = f"{int(time.time() * 1000)}"
|
||||
object_id = "cc85b"
|
||||
function_id = "balance_submitOrder_m"
|
||||
h5st = self.get_h5st(timestamp, encrypt_body, object_id, function_id)
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://trade.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://trade.m.jd.com/",
|
||||
"sec-ch-ua": '"Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": self.user_agent,
|
||||
"x-referer-page": "https://trade.m.jd.com/pay",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
data = {
|
||||
"scval": self.sku_id,
|
||||
"body": json.dumps(body),
|
||||
"t": timestamp,
|
||||
"h5st": h5st,
|
||||
"appid": "m_core",
|
||||
"client": "Win32",
|
||||
"clientVersion": "2.5.2",
|
||||
"d_brand": "",
|
||||
"d_model": "",
|
||||
"functionId": "balance_submitOrder_m",
|
||||
"lang": "zh-CN",
|
||||
"loginType": "2",
|
||||
"networkType": "false",
|
||||
"osVersion": "",
|
||||
"screen": "1152*2048",
|
||||
"sdkVersion": "",
|
||||
"uuid": "17573259367521124301157",
|
||||
"x-api-eid-token": self.x_token,
|
||||
"xAPIScval2": "wx",
|
||||
"xAPIScval3": "unknown",
|
||||
}
|
||||
url = "https://api.m.jd.com/client.action"
|
||||
proxy = self._get_proxy()
|
||||
response = self._session.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
verify=False,
|
||||
proxies=proxy,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"订单号:{self._order_num},app_store提交订单返回:{response.json()}"
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def run(self):
|
||||
self.x_token, self.eid = self.get_x_token()
|
||||
# 提交预付款订单
|
||||
self.get_current_order()
|
||||
order_res = self.submit_order()
|
||||
logger.info(
|
||||
f"订单号:{self._order_num},app_store提交预付款订单返回:{order_res}"
|
||||
)
|
||||
# 火爆
|
||||
if order_res.get("body", {}).get("errorCode") == "7201":
|
||||
return BusinessCode.JD_ORDER_RISK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(order_res),
|
||||
}
|
||||
# 未登录
|
||||
if order_res.get("body", {}).get("errorCode") == "302":
|
||||
return BusinessCode.JD_ORDER_CK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": order_res.get("body", {}).get("errorReason"),
|
||||
}
|
||||
# 无货
|
||||
if order_res.get("body", {}).get("errorCode") == "722":
|
||||
return BusinessCode.JD_ORDER_STOCK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": order_res.get("body", {})
|
||||
.get("submitOrderPromptVO", {})
|
||||
.get("title"),
|
||||
}
|
||||
# 火爆
|
||||
if order_res.get("body", {}).get("errorCode") == "601":
|
||||
return BusinessCode.JD_ORDER_RISK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(order_res),
|
||||
}
|
||||
if order_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(order_res),
|
||||
}
|
||||
if order_res.get("body", {}).get("errorCode") == "601":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(order_res),
|
||||
}
|
||||
order_id = order_res.get("body", {}).get("order", {}).get("orderId")
|
||||
# 获取支付信息
|
||||
pay_res = self.get_pay_res(order_id)
|
||||
logger.info(f"订单号:{self._order_num},app_store获取支付信息返回:{pay_res}")
|
||||
if (
|
||||
order_res.get("body", {}).get("errorCode") == "302"
|
||||
and order_res.get("body", {}).get("errorReason") == "未登录"
|
||||
):
|
||||
return BusinessCode.JD_ORDER_CK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(order_res),
|
||||
}
|
||||
if pay_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(pay_res),
|
||||
}
|
||||
pay_id = pay_res["body"]["payId"]
|
||||
# 获取微信支付信息
|
||||
return self.refresh_payment_url(pay_id, order_id)
|
||||
|
||||
def refresh_payment_url(self, pay_id: str, order_id: int):
|
||||
logger.info(f"订单号:{pay_id},app_store刷新支付链接 {order_id}")
|
||||
pay_channel_res = self.plat_pay_channel_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self._order_num},app_store请求微信渠道返回:{pay_channel_res}"
|
||||
)
|
||||
if pay_channel_res.get("code") == "601":
|
||||
return BusinessCode.JD_ORDER_RISK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(pay_channel_res),
|
||||
}
|
||||
if pay_channel_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(pay_channel_res),
|
||||
}
|
||||
wx_pay_res = self.plat_wx_pay_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self._order_num},app_store获取微信支付信息返回:{wx_pay_res}"
|
||||
)
|
||||
if wx_pay_res.get("code") != "0":
|
||||
return BusinessCode.JD_ORDER_NORMAL_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(wx_pay_res),
|
||||
}
|
||||
if wx_pay_res.get("errorCode") == "-1":
|
||||
return BusinessCode.JD_ORDER_CK_ERR, {
|
||||
"deeplink": "",
|
||||
"order_id": "",
|
||||
"pay_id": "",
|
||||
"remark": str(wx_pay_res),
|
||||
}
|
||||
return BusinessCode.SUCCESS, {
|
||||
"deeplink": wx_pay_res.get("payInfo", {}).get("mweb_url"),
|
||||
"order_id": str(order_id),
|
||||
"pay_id": str(pay_id),
|
||||
}
|
||||
237
apps/jd/services/delete.py
Normal file
237
apps/jd/services/delete.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
import execjs
|
||||
from curl_cffi import requests
|
||||
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class DeleteOrder:
|
||||
|
||||
def __init__(self, cookie, order_id):
|
||||
self.cookie = cookie
|
||||
self.order_id = order_id
|
||||
self.timestamp = int(time.time() * 1000)
|
||||
self.h5st_ctx = self.get_js_obj("h5st4.2.js")
|
||||
self.ai = "8108f"
|
||||
self.version = "4.2"
|
||||
self.p1 = None
|
||||
self.fp = None
|
||||
self.tk = None
|
||||
self.mode = None
|
||||
self.rd = None
|
||||
self.sign = None
|
||||
self.timestamp_sha256 = None
|
||||
self.t_sha256 = None
|
||||
self.t_send_data = None
|
||||
|
||||
def get_js_obj(self, js):
|
||||
with open(f"./js/{js}", "r", encoding="utf-8") as file:
|
||||
js_code = file.read()
|
||||
return execjs.compile(js_code, cwd="./node_modules")
|
||||
|
||||
def encrypt_body(self):
|
||||
key = "e7c398ffcb2d4824b4d0a703e38eb0bb"
|
||||
str_to_hash = f"{self.order_id}1sx{self.timestamp}{key}"
|
||||
enc_str = hashlib.md5(str_to_hash.encode("utf-8")).hexdigest()
|
||||
body = {
|
||||
"orderId": self.order_id,
|
||||
"source": "1",
|
||||
"channelSource": "sx",
|
||||
"t": self.timestamp,
|
||||
"encStr": enc_str,
|
||||
}
|
||||
return json.dumps(body)
|
||||
|
||||
def cancel_order(self):
|
||||
body = self.encrypt_body()
|
||||
url = "https://api.m.jd.com/api"
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://recharge.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://recharge.m.jd.com/",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
|
||||
"x-referer-page": "https://recharge.m.jd.com/orderDetail",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookie,
|
||||
}
|
||||
data = {
|
||||
"appid": "tsw-m",
|
||||
"functionId": "huafei_orderCancel",
|
||||
"t": f"{self.timestamp}",
|
||||
"body": body,
|
||||
"client": "iPhone",
|
||||
"uuid": "1735283651510338525254",
|
||||
"osVersion": "16.6",
|
||||
"screen": "828.0000123381615*1792.0000267028809",
|
||||
"x-api-eid-token": "jdd03MOMPSVKGBFF6WCM3KNQK34LGPGSCNKB2WACDOVKFUNXQWAWDEVXSHMGQEQLJ6EUKKXZ7ARQA4CPF6EMRRUP5P7ETLEAAAAMUC37NLWQAAAAADDQSI4QCFE6GAAX",
|
||||
}
|
||||
response = requests.post(url, headers=headers, data=data)
|
||||
print(response.text)
|
||||
|
||||
def get_fp(self):
|
||||
self.fp = self.h5st_ctx.call("iC")
|
||||
|
||||
def get_env(self):
|
||||
ctx = self.get_js_obj("h5st4.2.js")
|
||||
env = ctx.call("expandParams", self.fp, self.p1)
|
||||
return env
|
||||
|
||||
def request_algo(self):
|
||||
env = self.get_env()
|
||||
url = "https://cactus.jd.com/request_algo"
|
||||
params = {"g_ty": "ajax"}
|
||||
timestamp_ms = int(time.time() * 1000)
|
||||
data = {
|
||||
"version": "4.2",
|
||||
"fp": self.fp,
|
||||
"appId": "8108f",
|
||||
"timestamp": timestamp_ms,
|
||||
"platform": "web",
|
||||
"expandParams": env,
|
||||
"fv": "h5_npm_v4.2.0",
|
||||
}
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/json",
|
||||
"origin": "https://txsm-m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"referer": "https://txsm-m.jd.com/",
|
||||
"sec-ch-ua": '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
}
|
||||
data = json.dumps(data, separators=(",", ":"))
|
||||
response = requests.post(url, headers=headers, params=params, data=data)
|
||||
return response.json().get("data")
|
||||
|
||||
def get_algo(self):
|
||||
algo_data = self.request_algo()
|
||||
print(algo_data)
|
||||
algo = algo_data["result"]["algo"]
|
||||
self.tk = algo_data["result"]["tk"]
|
||||
self.mode = re.findall(r"algo\.(.*)\(", algo)[0]
|
||||
self.rd = re.findall(r"rd='(.*)';", algo)[0]
|
||||
|
||||
def get_p1(self):
|
||||
self.p1 = re.findall(r"pin=(.*);", self.cookie)[0]
|
||||
|
||||
def get_tm(self, timestamp):
|
||||
import datetime
|
||||
|
||||
# 将13位时间戳转换为datetime对象
|
||||
dt = datetime.datetime.fromtimestamp(timestamp / 1000)
|
||||
year = dt.year
|
||||
month = str(dt.month).zfill(2)
|
||||
day = str(dt.day).zfill(2)
|
||||
hour = str(dt.hour).zfill(2)
|
||||
minute = str(dt.minute).zfill(2)
|
||||
second = str(dt.second).zfill(2)
|
||||
# 将微秒转换为毫秒
|
||||
microsecond = dt.microsecond // 1000
|
||||
return f"{year}{month}{day}{hour}{minute}{second}{microsecond}"
|
||||
|
||||
def get_oe(self, ts):
|
||||
oe = self.h5st_ctx.call(
|
||||
"test", self.mode, self.tk, self.fp, ts, self.ai, self.rd
|
||||
)
|
||||
return oe
|
||||
|
||||
def get_body(self):
|
||||
self.t_send_data = int(time.time() * 1000)
|
||||
self.t_sha256 = int(time.time() * 1000)
|
||||
self.timestamp_sha256 = self.get_tm(self.t_sha256)
|
||||
ts = self.timestamp_sha256 + "74"
|
||||
oe = self.get_oe(ts)
|
||||
body_hex = self.h5st_ctx.call("encrypt_body", self.order_id, "delete")
|
||||
t_string = f"{oe}appid:m_core&body:{body_hex}&client:Win32&clientVersion:&functionId:order_recycle_m&t:{self.t_send_data}{oe}"
|
||||
self.sign = self.h5st_ctx.call("__genSign", t_string)
|
||||
body = self.h5st_ctx.call("build_params", self.order_id, "delete")
|
||||
return body
|
||||
|
||||
def get_h5st(self):
|
||||
h5st = f"{self.timestamp_sha256};{self.fp};{self.ai};{self.tk};{self.sign};{self.version};{self.t_sha256}"
|
||||
return h5st
|
||||
|
||||
def request_jd_recycle(self, body, h5st):
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://trade.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"referer": "https://trade.m.jd.com/",
|
||||
"sec-ch-ua": '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
"x-referer-page": "https://trade.m.jd.com/order/orderlist_jdm.shtml",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": self.cookie,
|
||||
}
|
||||
url = "https://api.m.jd.com/client.action"
|
||||
data = {
|
||||
"t": f"{self.t_send_data}",
|
||||
"loginType": "2",
|
||||
"loginWQBiz": "golden-trade",
|
||||
"appid": "m_core",
|
||||
"client": "Win32",
|
||||
"clientVersion": "",
|
||||
"build": "",
|
||||
"osVersion": "null",
|
||||
"screen": "1440*900",
|
||||
"networkType": "4g",
|
||||
"partner": "",
|
||||
"forcebot": "",
|
||||
"d_brand": "",
|
||||
"d_model": "",
|
||||
"lang": "zh-CN",
|
||||
"scope": "",
|
||||
"sdkVersion": "",
|
||||
"openudid": "",
|
||||
"uuid": "1658065822",
|
||||
"x-api-eid-token": "jdd03EZ4U2HD6OVXEWMTFQVIXNMASQWBFCYSPX37R7H6QIQDPWYYPIESUHF2YNEGTGXKWHGCH5VEWPMZA4PWPPKX6ZJOKWIAAAAMOR6CEGEIAAAAADWOVM6433SKKPAX",
|
||||
"functionId": "order_recycle_m",
|
||||
"body": body,
|
||||
"h5st": h5st,
|
||||
}
|
||||
response = requests.post(url, headers=headers, data=data)
|
||||
print(response.text)
|
||||
|
||||
def recycle_order(self):
|
||||
self.get_p1()
|
||||
self.get_fp()
|
||||
self.get_algo()
|
||||
body = self.get_body()
|
||||
h5st = self.get_h5st()
|
||||
self.request_jd_recycle(body=body, h5st=h5st)
|
||||
|
||||
def run(self):
|
||||
# 取消订单
|
||||
self.cancel_order()
|
||||
# 删除订单
|
||||
self.recycle_order()
|
||||
44
apps/jd/services/game_area.py
Normal file
44
apps/jd/services/game_area.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import time
|
||||
|
||||
from curl_cffi import requests
|
||||
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class GameArea:
|
||||
|
||||
@staticmethod
|
||||
def get_details(cookies, sku_id):
|
||||
headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"origin": "https://recharge.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://recharge.m.jd.com/",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
|
||||
"x-referer-page": "https://recharge.m.jd.com/cardSettlement",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"cookie": cookies,
|
||||
}
|
||||
params = {
|
||||
"appid": "tsw-m",
|
||||
"functionId": "getGameDetailBySkuId",
|
||||
"t": f"{int(time.time() * 1000)}",
|
||||
"body": '{"skuId":"%s","appKey":"apple","source":41}' % sku_id,
|
||||
"client": "iPhone",
|
||||
"uuid": "1731377463937218663686",
|
||||
"osVersion": "16.6",
|
||||
"screen": "1170.000046491623*2532.0001006126404",
|
||||
# "h5st": "20241112145441915;l055lflh9lr1i4k8;8e94a;tk03w92d21bda18nCxAp7H5xKn6xhgPjl2DU239CUZMhS1OwR9VyMZc5hQGRyFwDwYV8pE9DyQ7wpjquSVNSz5Kj3B5v;cbe697aecf5adbdd1ee2ddb4a1943f24;4.9;1731394481915;pjbMhjpdAaYR6jkQyLlQF6Ve2roQJrJdJrESJrpjh7Jf6rJdJz1TIipjLDrgJTISJSVS6PYd1jof0bFTKqIfJqoe1rYTImFf1LofzfITJrJdJrEa-OFTGOEjLrJp-jJS5ToeyT4e6nodGSld4j4TJeYe7PYS4bVT6T1fFSYTyjpjxj5PKSEQKeFjLrJp-jJf9HIg3T0UG6VRFuWeDipjxjJOJrpjh7JjbKUXzfUSnWYQfe2XJrJdJ31QHyVT5ipjLDrgJj4f9G1WJrJdJTlPJrpjh7ZMLrJp7rJdJLYOJipjLrpjh7JjJrJdJPYOJipjLrpjh7peLDIj1XETJrpjLrJp-rojxjZe2iFjLrpjLDrg7rJdJbYOJipjLrpjh7Je2rJdJfYOJipjLrpjh7Jf_rJdJjYOJipjLrpjh7Jj2zZf9rIjLDIj6XETJrpjLrJp-rojxj5R0ipjLrpjh7pfLDIj46FjLrpjLDrg7rJdJ7FjLrpjLDrg7rJdJb1OJrpjLrJpwqJdJbFQGakNGipjLDrguqpjhjZVl6VS5C2OqmHXi_1UHCFjLDIj6rEjLrpjLD7NLDIj7qEjLrJp-jpVLf2YLfVTeqZSAGlQLT4U1nojYunjGy1QDqWRLXmXoq5dGy1QDqWRJrJdJnVO4ipjLD7N;949f348da7bbb53fcee1f3a592db0ad3",
|
||||
"x-api-eid-token": "jdd03MOMPSVKGBFF6WCM3KNQK34LGPGSCNKB2WACDOVKFUNXQWAWDEVXSHMGQEQLJ6EUKKXZ7ARQA4CPF6EMRRUP5P7ETLEAAAAMTD4S5X6IAAAAADZXKZJY7RRZL4AX",
|
||||
}
|
||||
jd_api = "https://api.m.jd.com/api"
|
||||
response = requests.get(jd_api, headers=headers, params=params)
|
||||
return response.json()
|
||||
145
apps/jd/services/goods_apple_card.py
Normal file
145
apps/jd/services/goods_apple_card.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from curl_cffi import requests
|
||||
|
||||
from apps.jd.services.app_store import AppStoreSpider
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class GoodsAppleCard(AppStoreSpider):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cookies,
|
||||
order_num,
|
||||
brand_id,
|
||||
face_price,
|
||||
sku_id,
|
||||
username,
|
||||
game_srv=None,
|
||||
game_area=None,
|
||||
recharge_type=1,
|
||||
):
|
||||
super(GoodsAppleCard, self).__init__(cookies, order_num, face_price=face_price)
|
||||
self.sku_id = sku_id
|
||||
self.brand_id = brand_id
|
||||
self.username = username
|
||||
self.game_srv = game_srv
|
||||
self.game_area = game_area
|
||||
self.face_price = face_price
|
||||
self.recharge_type = recharge_type
|
||||
|
||||
def encrypt_username(self, username):
|
||||
key = "2E1ZMAF88CCE5EBE551FR3E9AA6FF322"
|
||||
username = self.js.call("encryptDes", username, key)
|
||||
return username
|
||||
|
||||
def submit_gp_order(self):
|
||||
headers = {
|
||||
"Host": "api.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"cache-control": "no-cache",
|
||||
"user-agent": self.__user_client,
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"x-referer-page": "https://recharge.m.jd.com/cardSettlement",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"x-rp-client": "h5_1.0.0",
|
||||
"origin": "https://recharge.m.jd.com",
|
||||
"sec-fetch-site": "same-site",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-dest": "empty",
|
||||
"referer": "https://recharge.m.jd.com/",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"priority": "u=1, i",
|
||||
"cookie": self.cookies,
|
||||
}
|
||||
username = self.encrypt_username(self.username)
|
||||
data = {}
|
||||
if self.recharge_type == 1:
|
||||
data = {
|
||||
"appid": "tsw-m",
|
||||
"functionId": "submitGPOrder",
|
||||
"t": f"{self.time_stamp}",
|
||||
"body": '{"skuId":"%s","brandId":"%s","type":2,"buyNum":1,"payMode":"0","totalPrice":"%s","username":"%s","appKey":"apple","source":41,"version":"1.10","orderSource":41}'
|
||||
% (self.sku_id, self.brand_id, self.face_price, username),
|
||||
"client": "iPhone",
|
||||
"uuid": "1731377463937218663686",
|
||||
"osVersion": "16.6",
|
||||
"screen": "1170.000046491623*2532.0001006126404",
|
||||
# "h5st": "20241112141707038;l055lflh9lr1i4k8;8e94a;tk03w92d21bda18nCxAp7H5xKn6xhgPjl2DU239CUZMhS1OwR9VyMZc5hQGRyFwDwYV8pE9DyQ7wpjquSVNSz5Kj3B5v;8effdf684f396d5889d2bde7e769f965;4.9;1731392227038;pjbMhjpdAaYR6jkQyLlQF6Ve2roQJrJdJrESJrpjh7Jf6rJdJz1TIipjLDrgJTISJSVS6PYd1jof0bFTKqIfJqoe1rYTImFf1LofzfITJrJdJrEa-OFTGOEjLrJp-jJS5ToeyT4e6nodGSld4j4TJeYe7PYS4bVT6T1fFSYTyjpjxj5PKSEQKeFjLrJp-jJf9HIg3T0UG6VRFuWeDipjxjJOJrpjh7JjxOYRhiFPyK3Z2f2XJrJdJ31QHyVT5ipjLDrgJj4f9G1WJrJdJTlPJrpjh7ZMLrJp7rJdJLYOJipjLrpjh7JjJrJdJPYOJipjLrpjh7ZeLDIj1XETJrpjLrJp-rojxjZe2iFjLrpjLDrg7rJdJbYOJipjLrpjh75e2rJdJfYOJipjLrpjh7Jf_rJdJjYOJipjLrpjh7Jj2zZf9rIjLDIj6XETJrpjLrJp-rojxj5R0ipjLrpjh7pfLDIj46FjLrpjLDrg7rJdJ7FjLrpjLDrg7rJdJb1OJrpjLrJpwqJdJbFQGakNGipjLDrguqpjhjZVl6VS5C2OqmHXi_1UHCFjLDIj6rEjLrpjLD7NLDIj7qEjLrJp-jpVLf2YLfVTeqZSAGlQLT4U1nojYunjGy1QDqWRLXmXoq5dGy1QDqWRJrJdJnVO4ipjLD7N;204790ef3f0380d87102b52131fd50d7",
|
||||
"x-api-eid-token": "jdd03MOMPSVKGBFF6WCM3KNQK34LGPGSCNKB2WACDOVKFUNXQWAWDEVXSHMGQEQLJ6EUKKXZ7ARQA4CPF6EMRRUP5P7ETLEAAAAMTDYRX6SIAAAAACB66T3PYQZLCEEX",
|
||||
}
|
||||
if self.recharge_type == 2:
|
||||
data = {
|
||||
"appid": "tsw-m",
|
||||
"functionId": "submitGPOrder",
|
||||
"t": f"{self.time_stamp}",
|
||||
"body": '{"skuId":"%s","brandId":"%s","type":2,"buyNum":1,"payMode":"0","totalPrice":"%s","gamesrv":"%s","gamearea":"%s","username":"%s","appKey":"apple","source":41,"version":"1.10","orderSource":41}'
|
||||
% (
|
||||
self.sku_id,
|
||||
self.brand_id,
|
||||
self.face_price,
|
||||
self.game_srv,
|
||||
self.game_area,
|
||||
username,
|
||||
),
|
||||
"client": "iPhone",
|
||||
"uuid": "1731377463937218663686",
|
||||
"osVersion": "16.6",
|
||||
"screen": "1170.000046491623*2532.0001006126404",
|
||||
# "h5st": "20241112172954252;l055lflh9lr1i4k8;8e94a;tk03w92d21bda18nCxAp7H5xKn6xhgPjl2DU239CUZMhS1OwR9VyMZc5hQGRyFwDwYV8pE9DyQ7wpjquSVNSz5Kj3B5v;dcf2dab0edd6fadd62550cc4ca5e28c4;4.9;1731403794252;pjbMhjpdAaYR6jkQyLlQF6Ve2roQJrJdJrESJrpjh7Jf6rJdJz1TIipjLDrgJTISJSVS6PYd1jof0bFTKqIfJqoe1rYTImFf1LofzfITJrJdJrEa-OFTGOEjLrJp-jJS5ToeyT4e6nodGSld4j4TJeYe7PYS4bVT6T1fFSYTyjpjxj5PKSEQKeFjLrJp-jJf9HIg3T0UG6VRFuWeDipjxjJOJrpjh7JjWW1f5LnageHNqSEOJrJdJ31QHyVT5ipjLDrgJj4f9G1WJrJdJTlPJrpjh7ZMLrJp7rJdJLYOJipjLrpjh7JjJrJdJPYOJipjLrpjh7ZeLDIj1XETJrpjLrJp-rojxjZe2iFjLrpjLDrg7rJdJbYOJipjLrpjh75e2rJdJfYOJipjLrpjh7Jf_rJdJjYOJipjLrpjh7Jj2zZf9rIjLDIj6XETJrpjLrJp-rojxj5R0ipjLrpjh7pfLDIj46FjLrpjLDrg7rJdJ7FjLrpjLDrg7rJdJb1OJrpjLrJpwqJdJbFQGakNGipjLDrguqpjhjZVl6VS5C2OqmHXi_1UHCFjLDIj6rEjLrpjLD7NLDIj7qEjLrJp-jpVLf2YLfVTeqZSAGlQLT4U1nojYunjGy1QDqWRLXmXoq5dGy1QDqWRJrJdJnVO4ipjLD7N;651ee3204b43d0d514092fa61f5629f1",
|
||||
"x-api-eid-token": "jdd03MOMPSVKGBFF6WCM3KNQK34LGPGSCNKB2WACDOVKFUNXQWAWDEVXSHMGQEQLJ6EUKKXZ7ARQA4CPF6EMRRUP5P7ETLEAAAAMTD6ZVZVQAAAAADSCNVUR4IR7W54X",
|
||||
}
|
||||
response = requests.post(self.jd_api, headers=headers, data=data)
|
||||
return response.json()
|
||||
|
||||
def run(self):
|
||||
# 提交预付款订单
|
||||
gp_order_res = self.submit_gp_order()
|
||||
# gp_order_res = {'result': {'orderId': 304636667875, 'paySuccessUrl': ''}, 'code': '0'}
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},商品下单提交预付款订单返回:{gp_order_res}"
|
||||
)
|
||||
if gp_order_res.get("code") != "0":
|
||||
return 110, gp_order_res
|
||||
order_id = gp_order_res["result"]["orderId"]
|
||||
|
||||
# 获取支付信息
|
||||
pay_res_ = self.get_pay_res(order_id)
|
||||
logger.info(f"订单号:{self.order_num},商品下单获取支付信息返回:{pay_res_}")
|
||||
if pay_res_.get("code") != "0":
|
||||
return 110, pay_res_
|
||||
pay_id = pay_res_["body"]["payId"]
|
||||
|
||||
# 获取微信支付信息
|
||||
pay_channel_res = self.plat_pay_channel_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},商品下单请求微信渠道返回:{pay_channel_res}"
|
||||
)
|
||||
if pay_channel_res.get("code") != "0":
|
||||
return 110, pay_channel_res
|
||||
wx_pay_res = self.plat_wx_pay_res(pay_id)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},商品下单获取微信支付信息返回:{wx_pay_res}"
|
||||
)
|
||||
if wx_pay_res.get("code") != "0":
|
||||
return 110, wx_pay_res
|
||||
if wx_pay_res.get("errorCode") == "-1":
|
||||
wx_pay_res["order_id"] = order_id
|
||||
wx_pay_res["pay_id"] = pay_id
|
||||
wx_pay_res["face_price"] = self.face_price
|
||||
return 110, wx_pay_res
|
||||
mweb_url = wx_pay_res["payInfo"]["mweb_url"]
|
||||
# 获取支付链接信息
|
||||
deep_link_res = self.get_deep_link_res(mweb_url)
|
||||
logger.info(
|
||||
f"订单号:{self.order_num},商品下单获取支付链接deep_link信息返回:{deep_link_res}"
|
||||
)
|
||||
if deep_link_res.get("retcode") != 1:
|
||||
return 110, deep_link_res
|
||||
return 100, {
|
||||
"deeplink": deep_link_res["deeplink"],
|
||||
"order_id": pay_channel_res["orderId"],
|
||||
"pay_id": pay_id,
|
||||
"face_price": self.face_price,
|
||||
}
|
||||
690
apps/jd/services/jstk.py
Normal file
690
apps/jd/services/jstk.py
Normal file
@@ -0,0 +1,690 @@
|
||||
import hashlib
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import urllib
|
||||
from abc import ABC, abstractmethod
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from curl_cffi import requests
|
||||
|
||||
|
||||
class JsTkBase(ABC):
|
||||
def __init__(self):
|
||||
self._eid = ""
|
||||
self._token = ""
|
||||
|
||||
def _get_sign(self, text: str) -> str:
|
||||
text_bytes = text.encode("utf-8")
|
||||
md5_str = hashlib.md5(text_bytes).hexdigest()
|
||||
return md5_str
|
||||
|
||||
def get_token(self):
|
||||
return self._token
|
||||
|
||||
def get_eid(self):
|
||||
return self._eid
|
||||
|
||||
def _td_encrypt(self, e):
|
||||
"""
|
||||
将输入 e 按照给定的JS逻辑进行 JSON化 -> URL编码 -> 自定义Base64编码 -> 加斜杠
|
||||
|
||||
Args:
|
||||
e: 输入的数据 (可以是字典、列表、字符串、数字等,会被JSON序列化)
|
||||
|
||||
Returns:
|
||||
str: 编码后的字符串,末尾带 "/"
|
||||
"""
|
||||
# 1. 自定义字母表,对应 Base64 的 64 个字符
|
||||
# JS中的 'u' 是 0-63 的索引对应的字符
|
||||
custom_alphabet = (
|
||||
"23IL<N01c7KvwZO56RSTAfghiFyzWJqVabGH4PQdopUrsCuX*xeBjkltDEmn89.-"
|
||||
)
|
||||
# JS代码中索引 64 被用于填充字符。虽然字母表里没有 '=',
|
||||
# 但标准Base64用 '=' 填充,且JS代码逻辑中使用了 64,推测 64 对应填充字符 '='
|
||||
padding_char = "="
|
||||
|
||||
# 2. 将输入 e 转换为 JSON 字符串
|
||||
s = json.dumps(e, separators=(",", ":"))
|
||||
|
||||
# 3. 对 JSON 字符串进行 URL 编码
|
||||
# encodeURIComponent 编码所有字符,除了 ASCII 字母、数字、- _ . ! ~ * ' ( )
|
||||
# Python 的 urllib.parse.quote 需要指定 safe 字符集来模拟
|
||||
s_encoded = urllib.parse.quote(
|
||||
s,
|
||||
safe="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()",
|
||||
)
|
||||
|
||||
# 获取 URL 编码后字符串的字节表示,因为 Base64 编码是基于字节的
|
||||
s_bytes = s_encoded.encode("utf-8")
|
||||
|
||||
# 4. 执行自定义的 Base64 编码
|
||||
encoded_string = ""
|
||||
d = 0 # 字节索引
|
||||
s_len = len(s_bytes)
|
||||
|
||||
while d < s_len:
|
||||
# 取 3 个字节(可能不足 3 个在末尾)
|
||||
byte1 = s_bytes[d]
|
||||
d += 1
|
||||
byte2 = s_bytes[d] if d < s_len else None
|
||||
d += 1
|
||||
byte3 = s_bytes[d] if d < s_len else None
|
||||
d += 1
|
||||
|
||||
# 将 3 个字节(24 位)分成 4 组,每组 6 位
|
||||
# 索引 1: byte1 的前 6 位
|
||||
idx1 = (byte1 >> 2) & 63
|
||||
|
||||
# 索引 2: byte1 的后 2 位 + byte2 的前 4 位
|
||||
idx2 = (byte1 & 3) << 4
|
||||
if byte2 is not None:
|
||||
idx2 |= (byte2 >> 4) & 15
|
||||
# else: 如果 byte2 不存在,这里只使用了 byte1 的后 2 位,高位补 0
|
||||
|
||||
# 索引 3: byte2 的后 4 位 + byte3 的前 2 位
|
||||
idx3 = 64 # 默认填充
|
||||
if byte2 is not None:
|
||||
idx3 = (byte2 & 15) << 2
|
||||
if byte3 is not None:
|
||||
idx3 |= (byte3 >> 6) & 3
|
||||
# else: 如果 byte3 不存在,只使用了 byte2 的后 4 位,高位补 0
|
||||
# else: 如果 byte2 不存在,保持填充状态 64
|
||||
|
||||
# 索引 4: byte3 的后 6 位
|
||||
idx4 = 64 # 默认填充
|
||||
if byte3 is not None:
|
||||
idx4 = byte3 & 63
|
||||
# else: 如果 byte3 不存在,保持填充状态 64
|
||||
|
||||
# 根据索引查找字符或使用填充符
|
||||
encoded_string += custom_alphabet[idx1]
|
||||
encoded_string += custom_alphabet[idx2]
|
||||
|
||||
if idx3 == 64:
|
||||
encoded_string += padding_char
|
||||
else:
|
||||
encoded_string += custom_alphabet[idx3]
|
||||
|
||||
if idx4 == 64:
|
||||
encoded_string += padding_char
|
||||
else:
|
||||
encoded_string += custom_alphabet[idx4]
|
||||
|
||||
# 5. 在末尾添加 "/"
|
||||
return encoded_string + "/"
|
||||
|
||||
def _request(self, headers: dict, data: dict):
|
||||
response = requests.post(
|
||||
"https://jra.jd.com/jsTk.do", headers=headers, data=data
|
||||
)
|
||||
result = response.json()
|
||||
self._token = result.get("data", {}).get("token")
|
||||
self._eid = result.get("data", {}).get("eid")
|
||||
|
||||
@abstractmethod
|
||||
def generate_token(self, user_agent: str, cookie: str = "") -> str:
|
||||
pass
|
||||
|
||||
|
||||
class NormalJsTk(JsTkBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__biz_id = "gold_m"
|
||||
self.__o = "m.jd.com/"
|
||||
|
||||
def generate_token(self, user_agent: str, cookie: str = ""):
|
||||
ctime = int(time.time() * 1000)
|
||||
app_version = user_agent.replace("Mozilla/", "")
|
||||
|
||||
d_param = {
|
||||
"ts": {
|
||||
"deviceTime": ctime,
|
||||
"deviceEndTime": ctime + random.randint(50, 150),
|
||||
},
|
||||
"ca": {
|
||||
"tdHash": "af7c21fe564a8cf177fcc77ce8847eb0",
|
||||
"contextName": "webgl,experimental-webgl",
|
||||
"webglversion": "WebGL 1.0 (OpenGL ES 2.0 Chromium)",
|
||||
"shadingLV": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)",
|
||||
"vendor": "WebKit",
|
||||
"renderer": "WebKit WebGL",
|
||||
"extensions": [
|
||||
"ANGLE_instanced_arrays",
|
||||
"EXT_blend_minmax",
|
||||
"EXT_clip_control",
|
||||
"EXT_color_buffer_half_float",
|
||||
"EXT_depth_clamp",
|
||||
"EXT_disjoint_timer_query",
|
||||
"EXT_float_blend",
|
||||
"EXT_frag_depth",
|
||||
"EXT_polygon_offset_clamp",
|
||||
"EXT_shader_texture_lod",
|
||||
"EXT_texture_compression_bptc",
|
||||
"EXT_texture_compression_rgtc",
|
||||
"EXT_texture_filter_anisotropic",
|
||||
"EXT_texture_mirror_clamp_to_edge",
|
||||
"EXT_sRGB",
|
||||
"KHR_parallel_shader_compile",
|
||||
"OES_element_index_uint",
|
||||
"OES_fbo_render_mipmap",
|
||||
"OES_standard_derivatives",
|
||||
"OES_texture_float",
|
||||
"OES_texture_float_linear",
|
||||
"OES_texture_half_float",
|
||||
"OES_texture_half_float_linear",
|
||||
"OES_vertex_array_object",
|
||||
"WEBGL_blend_func_extended",
|
||||
"WEBGL_color_buffer_float",
|
||||
"WEBGL_compressed_texture_astc",
|
||||
"WEBGL_compressed_texture_etc",
|
||||
"WEBGL_compressed_texture_etc1",
|
||||
"WEBGL_compressed_texture_pvrtc",
|
||||
"WEBGL_compressed_texture_s3tc",
|
||||
"WEBGL_compressed_texture_s3tc_srgb",
|
||||
"WEBGL_debug_renderer_info",
|
||||
"WEBGL_debug_shaders",
|
||||
"WEBGL_depth_texture",
|
||||
"WEBGL_draw_buffers",
|
||||
"WEBGL_lose_context",
|
||||
"WEBGL_multi_draw",
|
||||
"WEBGL_polygon_mode",
|
||||
],
|
||||
"wuv": "Google Inc. (Apple)",
|
||||
"wur": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)",
|
||||
},
|
||||
"m": {"compatMode": "CSS1Compat"},
|
||||
"n": {
|
||||
"vendorSub": "",
|
||||
"productSub": "20030107",
|
||||
"vendor": "Google Inc.",
|
||||
"maxTouchPoints": 0,
|
||||
"doNotTrack": "1",
|
||||
"pdfViewerEnabled": True,
|
||||
"hardwareConcurrency": 10,
|
||||
"cookieEnabled": True,
|
||||
"appCodeName": "Mozilla",
|
||||
"appName": "Netscape",
|
||||
"appVersion": app_version,
|
||||
"platform": "MacIntel",
|
||||
"product": "Gecko",
|
||||
"userAgent": user_agent,
|
||||
"language": "zh-CN",
|
||||
"onLine": True,
|
||||
"webdriver": False,
|
||||
"javaEnabled": False,
|
||||
"deprecatedRunAdAuctionEnforcesKAnonymity": False,
|
||||
"deviceMemory": 8,
|
||||
"enumerationOrder": [
|
||||
"vendorSub",
|
||||
"productSub",
|
||||
"vendor",
|
||||
"maxTouchPoints",
|
||||
"scheduling",
|
||||
"userActivation",
|
||||
"doNotTrack",
|
||||
"geolocation",
|
||||
"connection",
|
||||
"plugins",
|
||||
"mimeTypes",
|
||||
"pdfViewerEnabled",
|
||||
"webkitTemporaryStorage",
|
||||
"webkitPersistentStorage",
|
||||
"windowControlsOverlay",
|
||||
"hardwareConcurrency",
|
||||
"cookieEnabled",
|
||||
"appCodeName",
|
||||
"appName",
|
||||
"appVersion",
|
||||
"platform",
|
||||
"product",
|
||||
"userAgent",
|
||||
"language",
|
||||
"languages",
|
||||
"onLine",
|
||||
"webdriver",
|
||||
"getGamepads",
|
||||
"javaEnabled",
|
||||
"sendBeacon",
|
||||
"vibrate",
|
||||
"deprecatedRunAdAuctionEnforcesKAnonymity",
|
||||
"protectedAudience",
|
||||
"bluetooth",
|
||||
"storageBuckets",
|
||||
"clipboard",
|
||||
"credentials",
|
||||
"keyboard",
|
||||
"managed",
|
||||
"mediaDevices",
|
||||
"storage",
|
||||
"serviceWorker",
|
||||
"virtualKeyboard",
|
||||
"wakeLock",
|
||||
"deviceMemory",
|
||||
"userAgentData",
|
||||
"login",
|
||||
"ink",
|
||||
"mediaCapabilities",
|
||||
"devicePosture",
|
||||
"hid",
|
||||
"locks",
|
||||
"gpu",
|
||||
"mediaSession",
|
||||
"permissions",
|
||||
"presentation",
|
||||
"serial",
|
||||
"usb",
|
||||
"xr",
|
||||
"adAuctionComponents",
|
||||
"runAdAuction",
|
||||
"canLoadAdAuctionFencedFrame",
|
||||
"canShare",
|
||||
"share",
|
||||
"clearAppBadge",
|
||||
"getBattery",
|
||||
"getUserMedia",
|
||||
"requestMIDIAccess",
|
||||
"requestMediaKeySystemAccess",
|
||||
"setAppBadge",
|
||||
"webkitGetUserMedia",
|
||||
"clearOriginJoinedAdInterestGroups",
|
||||
"createAuctionNonce",
|
||||
"joinAdInterestGroup",
|
||||
"leaveAdInterestGroup",
|
||||
"updateAdInterestGroups",
|
||||
"deprecatedReplaceInURN",
|
||||
"deprecatedURNToURL",
|
||||
"getInstalledRelatedApps",
|
||||
"getInterestGroupAdAuctionData",
|
||||
"registerProtocolHandler",
|
||||
"unregisterProtocolHandler",
|
||||
],
|
||||
},
|
||||
"p": [
|
||||
{"name": "PDF Viewer"},
|
||||
{"name": "Chrome PDF Viewer"},
|
||||
{"name": "Chromium PDF Viewer"},
|
||||
{"name": "Microsoft Edge PDF Viewer"},
|
||||
{"name": "WebKit built-in PDF"},
|
||||
],
|
||||
"w": {"devicePixelRatio": 2, "screenTop": 0, "screenLeft": 0},
|
||||
"s": {
|
||||
"availHeight": 1080,
|
||||
"availWidth": 1920,
|
||||
"colorDepth": 30,
|
||||
"height": 1080,
|
||||
"width": 1920,
|
||||
"pixelDepth": 30,
|
||||
},
|
||||
"sc": {},
|
||||
"ss": {
|
||||
"cookie": True,
|
||||
"localStorage": True,
|
||||
"sessionStorage": True,
|
||||
"globalStorage": False,
|
||||
"indexedDB": True,
|
||||
},
|
||||
"tz": -480,
|
||||
"lil": "",
|
||||
"wil": "",
|
||||
"wi": {
|
||||
"ow": 1920,
|
||||
"oh": 1080,
|
||||
"iw": 274,
|
||||
"ih": 959,
|
||||
"etn": "[object External]",
|
||||
},
|
||||
}
|
||||
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
"origin": "https://shop.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://shop.m.jd.com/",
|
||||
"sec-ch-ua-mobile": "?1",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": user_agent,
|
||||
}
|
||||
|
||||
data = {
|
||||
"d": self._td_encrypt(d_param),
|
||||
}
|
||||
|
||||
a_param = {
|
||||
"pin": "",
|
||||
"oid": "",
|
||||
"bizId": self.__biz_id,
|
||||
"fc": self._eid if self._eid else "",
|
||||
"mode": "strict",
|
||||
"p": "s",
|
||||
"fp": "6d2c8c60af349b1a87ddd3b065194ca4",
|
||||
"ctype": "1",
|
||||
"v": "4.2.8.0",
|
||||
"pv": "02_mt_5LXK_60653970589",
|
||||
"f": "3",
|
||||
"s": self._get_sign(data["d"] + "_*_UYBN6YGTNO6DHPVB"),
|
||||
"o": self.__o,
|
||||
"qs": "",
|
||||
"jsTk": "",
|
||||
"qi": "",
|
||||
}
|
||||
data["a"] = self._td_encrypt(a_param)
|
||||
response = self._request(headers, data)
|
||||
print(response)
|
||||
|
||||
|
||||
class TxSMJsTk(JsTkBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__url = "https://txsm-m.jd.com/?babelChannel=ttt35"
|
||||
self.__biz_id = "jdtxsm"
|
||||
self.__jd_risk_token_id = ""
|
||||
self.__jd_jr_td_risk_pin = ""
|
||||
self.__o = "txsm-m.jd.com/"
|
||||
|
||||
def get_jd_risk_token_id(self):
|
||||
return self.__jd_risk_token_id
|
||||
|
||||
def get_jd_jr_td_risk_pin(self):
|
||||
return self.__jd_jr_td_risk_pin
|
||||
|
||||
def generate_risk_pin(self, user_agent: str, cookie: str):
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cookie": cookie,
|
||||
"user-agent": user_agent,
|
||||
}
|
||||
response = requests.get("https://gia.jd.com/m.html", headers=headers)
|
||||
text = response.text.replace(" ", "")
|
||||
token = re.findall(r"jd_risk_token_id='(.*?)';", text)
|
||||
if token:
|
||||
self.__jd_risk_token_id = token[0]
|
||||
# 正则表达式解析后面
|
||||
token = re.findall(r"jd_jr_td_risk_pin='(.*?)';", text)
|
||||
if token:
|
||||
self.__jd_jr_td_risk_pin = token[0]
|
||||
|
||||
def generate_token(self, user_agent: str, cookie: str = ""):
|
||||
self.generate_risk_pin(user_agent, cookie)
|
||||
ctime = int(time.time() * 1000)
|
||||
app_version = user_agent.replace("Mozilla/", "")
|
||||
|
||||
d_param = {
|
||||
"ts": {
|
||||
"deviceTime": ctime,
|
||||
"deviceEndTime": ctime + random.randint(50, 150),
|
||||
},
|
||||
"ca": {
|
||||
"tdHash": "af7c21fe564a8cf177fcc77ce8847eb0",
|
||||
"contextName": "webgl,experimental-webgl",
|
||||
"webglversion": "WebGL 1.0 (OpenGL ES 2.0 Chromium)",
|
||||
"shadingLV": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)",
|
||||
"vendor": "WebKit",
|
||||
"renderer": "WebKit WebGL",
|
||||
"extensions": [
|
||||
"ANGLE_instanced_arrays",
|
||||
"EXT_blend_minmax",
|
||||
"EXT_clip_control",
|
||||
"EXT_color_buffer_half_float",
|
||||
"EXT_depth_clamp",
|
||||
"EXT_disjoint_timer_query",
|
||||
"EXT_float_blend",
|
||||
"EXT_frag_depth",
|
||||
"EXT_polygon_offset_clamp",
|
||||
"EXT_shader_texture_lod",
|
||||
"EXT_texture_compression_bptc",
|
||||
"EXT_texture_compression_rgtc",
|
||||
"EXT_texture_filter_anisotropic",
|
||||
"EXT_texture_mirror_clamp_to_edge",
|
||||
"EXT_sRGB",
|
||||
"KHR_parallel_shader_compile",
|
||||
"OES_element_index_uint",
|
||||
"OES_fbo_render_mipmap",
|
||||
"OES_standard_derivatives",
|
||||
"OES_texture_float",
|
||||
"OES_texture_float_linear",
|
||||
"OES_texture_half_float",
|
||||
"OES_texture_half_float_linear",
|
||||
"OES_vertex_array_object",
|
||||
"WEBGL_blend_func_extended",
|
||||
"WEBGL_color_buffer_float",
|
||||
"WEBGL_compressed_texture_astc",
|
||||
"WEBGL_compressed_texture_etc",
|
||||
"WEBGL_compressed_texture_etc1",
|
||||
"WEBGL_compressed_texture_pvrtc",
|
||||
"WEBGL_compressed_texture_s3tc",
|
||||
"WEBGL_compressed_texture_s3tc_srgb",
|
||||
"WEBGL_debug_renderer_info",
|
||||
"WEBGL_debug_shaders",
|
||||
"WEBGL_depth_texture",
|
||||
"WEBGL_draw_buffers",
|
||||
"WEBGL_lose_context",
|
||||
"WEBGL_multi_draw",
|
||||
"WEBGL_polygon_mode",
|
||||
],
|
||||
"wuv": "Google Inc. (Apple)",
|
||||
"wur": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)",
|
||||
},
|
||||
"m": {"compatMode": "CSS1Compat"},
|
||||
"n": {
|
||||
"vendorSub": "",
|
||||
"productSub": "20030107",
|
||||
"vendor": "Google Inc.",
|
||||
"maxTouchPoints": 0,
|
||||
"doNotTrack": "1",
|
||||
"pdfViewerEnabled": True,
|
||||
"hardwareConcurrency": 10,
|
||||
"cookieEnabled": True,
|
||||
"appCodeName": "Mozilla",
|
||||
"appName": "Netscape",
|
||||
"appVersion": app_version,
|
||||
"platform": "MacIntel",
|
||||
"product": "Gecko",
|
||||
"userAgent": user_agent,
|
||||
"language": "zh-CN",
|
||||
"onLine": True,
|
||||
"webdriver": False,
|
||||
"javaEnabled": False,
|
||||
"deprecatedRunAdAuctionEnforcesKAnonymity": False,
|
||||
"deviceMemory": 8,
|
||||
"enumerationOrder": [
|
||||
"vendorSub",
|
||||
"productSub",
|
||||
"vendor",
|
||||
"maxTouchPoints",
|
||||
"scheduling",
|
||||
"userActivation",
|
||||
"doNotTrack",
|
||||
"geolocation",
|
||||
"connection",
|
||||
"plugins",
|
||||
"mimeTypes",
|
||||
"pdfViewerEnabled",
|
||||
"webkitTemporaryStorage",
|
||||
"webkitPersistentStorage",
|
||||
"windowControlsOverlay",
|
||||
"hardwareConcurrency",
|
||||
"cookieEnabled",
|
||||
"appCodeName",
|
||||
"appName",
|
||||
"appVersion",
|
||||
"platform",
|
||||
"product",
|
||||
"userAgent",
|
||||
"language",
|
||||
"languages",
|
||||
"onLine",
|
||||
"webdriver",
|
||||
"getGamepads",
|
||||
"javaEnabled",
|
||||
"sendBeacon",
|
||||
"vibrate",
|
||||
"deprecatedRunAdAuctionEnforcesKAnonymity",
|
||||
"protectedAudience",
|
||||
"bluetooth",
|
||||
"storageBuckets",
|
||||
"clipboard",
|
||||
"credentials",
|
||||
"keyboard",
|
||||
"managed",
|
||||
"mediaDevices",
|
||||
"storage",
|
||||
"serviceWorker",
|
||||
"virtualKeyboard",
|
||||
"wakeLock",
|
||||
"deviceMemory",
|
||||
"userAgentData",
|
||||
"login",
|
||||
"ink",
|
||||
"mediaCapabilities",
|
||||
"devicePosture",
|
||||
"hid",
|
||||
"locks",
|
||||
"gpu",
|
||||
"mediaSession",
|
||||
"permissions",
|
||||
"presentation",
|
||||
"serial",
|
||||
"usb",
|
||||
"xr",
|
||||
"adAuctionComponents",
|
||||
"runAdAuction",
|
||||
"canLoadAdAuctionFencedFrame",
|
||||
"canShare",
|
||||
"share",
|
||||
"clearAppBadge",
|
||||
"getBattery",
|
||||
"getUserMedia",
|
||||
"requestMIDIAccess",
|
||||
"requestMediaKeySystemAccess",
|
||||
"setAppBadge",
|
||||
"webkitGetUserMedia",
|
||||
"clearOriginJoinedAdInterestGroups",
|
||||
"createAuctionNonce",
|
||||
"joinAdInterestGroup",
|
||||
"leaveAdInterestGroup",
|
||||
"updateAdInterestGroups",
|
||||
"deprecatedReplaceInURN",
|
||||
"deprecatedURNToURL",
|
||||
"getInstalledRelatedApps",
|
||||
"getInterestGroupAdAuctionData",
|
||||
"registerProtocolHandler",
|
||||
"unregisterProtocolHandler",
|
||||
],
|
||||
},
|
||||
"p": [
|
||||
{"name": "PDF Viewer"},
|
||||
{"name": "Chrome PDF Viewer"},
|
||||
{"name": "Chromium PDF Viewer"},
|
||||
{"name": "Microsoft Edge PDF Viewer"},
|
||||
{"name": "WebKit built-in PDF"},
|
||||
],
|
||||
"w": {"devicePixelRatio": 2, "screenTop": 0, "screenLeft": 0},
|
||||
"s": {
|
||||
"availHeight": 1080,
|
||||
"availWidth": 1920,
|
||||
"colorDepth": 30,
|
||||
"height": 1080,
|
||||
"width": 1920,
|
||||
"pixelDepth": 30,
|
||||
},
|
||||
"sc": {
|
||||
"ActiveBorder": "rgb(0, 0, 0)",
|
||||
"ActiveCaption": "rgb(0, 0, 0)",
|
||||
"AppWorkspace": "rgb(255, 255, 255)",
|
||||
"Background": "rgb(255, 255, 255)",
|
||||
"ButtonFace": "rgb(239, 239, 239)",
|
||||
"ButtonHighlight": "rgb(239, 239, 239)",
|
||||
"ButtonShadow": "rgb(239, 239, 239)",
|
||||
"ButtonText": "rgb(0, 0, 0)",
|
||||
"CaptionText": "rgb(0, 0, 0)",
|
||||
"GrayText": "rgb(128, 128, 128)",
|
||||
"Highlight": "rgba(128, 188, 254, 0.6)",
|
||||
"HighlightText": "rgb(0, 0, 0)",
|
||||
"InactiveBorder": "rgb(0, 0, 0)",
|
||||
"InactiveCaption": "rgb(255, 255, 255)",
|
||||
"InactiveCaptionText": "rgb(128, 128, 128)",
|
||||
"InfoBackground": "rgb(255, 255, 255)",
|
||||
"InfoText": "rgb(0, 0, 0)",
|
||||
"Menu": "rgb(255, 255, 255)",
|
||||
"MenuText": "rgb(0, 0, 0)",
|
||||
"Scrollbar": "rgb(255, 255, 255)",
|
||||
"ThreeDDarkShadow": "rgb(0, 0, 0)",
|
||||
"ThreeDFace": "rgb(239, 239, 239)",
|
||||
"ThreeDHighlight": "rgb(0, 0, 0)",
|
||||
"ThreeDLightShadow": "rgb(0, 0, 0)",
|
||||
"ThreeDShadow": "rgb(0, 0, 0)",
|
||||
"Window": "rgb(255, 255, 255)",
|
||||
"WindowFrame": "rgb(0, 0, 0)",
|
||||
"WindowText": "rgb(0, 0, 0)",
|
||||
},
|
||||
"ss": {
|
||||
"cookie": True,
|
||||
"localStorage": True,
|
||||
"sessionStorage": True,
|
||||
"globalStorage": False,
|
||||
"indexedDB": True,
|
||||
},
|
||||
"tz": -480,
|
||||
"lil": "",
|
||||
"wil": "",
|
||||
"wi": {
|
||||
"ow": 1920,
|
||||
"oh": 1080,
|
||||
"iw": 397,
|
||||
"ih": 959,
|
||||
"etn": "[object External]",
|
||||
},
|
||||
}
|
||||
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
"origin": "https://shop.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://shop.m.jd.com/",
|
||||
"sec-ch-ua-mobile": "?1",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": user_agent,
|
||||
}
|
||||
|
||||
data = {
|
||||
"d": self._td_encrypt(d_param),
|
||||
}
|
||||
|
||||
if not self.__jd_jr_td_risk_pin and self._token:
|
||||
self.__jd_jr_td_risk_pin = self._token
|
||||
|
||||
a_param = {
|
||||
"pin": self.__jd_jr_td_risk_pin if self.__jd_jr_td_risk_pin else "",
|
||||
"oid": "",
|
||||
"bizId": self.__biz_id,
|
||||
"fc": self._eid if self._eid else "",
|
||||
"mode": "strict",
|
||||
"p": "s",
|
||||
"fp": "6d2c8c60af349b1a87ddd3b065194ca4",
|
||||
"ctype": "1",
|
||||
"v": "4.2.8.0",
|
||||
"pv": "02_mt_5LXK_60653970589",
|
||||
"f": "3",
|
||||
"s": self._get_sign(data["d"] + "_*_UYBN6YGTNO6DHPVB"),
|
||||
"o": self.__o,
|
||||
"qs": urlparse(self.__url).query,
|
||||
"jsTk": "",
|
||||
"qi": "",
|
||||
}
|
||||
data["a"] = self._td_encrypt(a_param)
|
||||
self._request(headers, data)
|
||||
329
apps/jd/services/login.py
Normal file
329
apps/jd/services/login.py
Normal file
@@ -0,0 +1,329 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
|
||||
import ddddocr
|
||||
import execjs
|
||||
from curl_cffi import requests
|
||||
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class LoginSpider:
|
||||
|
||||
def __init__(self, phone_):
|
||||
self.phone = phone_
|
||||
self.headers = {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"origin": "https://plogin.m.jd.com",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://plogin.m.jd.com/login/login?appid=300&returnurl=https%3A%2F%2Fm.jd.com%2F&source=wq_passport",
|
||||
"sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
|
||||
}
|
||||
self.cookies = {}
|
||||
self.current_os = platform.system()
|
||||
self.js_path = None
|
||||
self.node_modules_path = None
|
||||
self.load_directory()
|
||||
self.slide_ctw = self.init_verify_data_js()
|
||||
self.user_ctw = self.init_user_encrypt_js()
|
||||
self.h5st_ctw = self.init_h5st_js()
|
||||
|
||||
self.session = requests.Session()
|
||||
self.ocr = ddddocr.DdddOcr()
|
||||
|
||||
self.eid = ""
|
||||
self.fp = "26114226dc0c6ee7f1eaf3d4abb30cf2"
|
||||
self.s_token = None
|
||||
self.rsa_modulus = None
|
||||
self.api_st = None
|
||||
self.api_fp = None
|
||||
self.img1 = None
|
||||
self.img2 = None
|
||||
self.vt = None
|
||||
self.img = None
|
||||
self.jd_risk_token_id = None
|
||||
self.jcap_sid = None
|
||||
self.ck = None
|
||||
|
||||
def load_directory(self):
|
||||
if self.current_os == "Linux":
|
||||
self.js_path = r"/app/js"
|
||||
self.node_modules_path = r"/app/node_modules"
|
||||
else:
|
||||
self.js_path = "js"
|
||||
self.node_modules_path = "./node_modules"
|
||||
|
||||
def do_execjs(self, path):
|
||||
return execjs.compile(
|
||||
open(path, encoding="utf8").read(), cwd=self.node_modules_path
|
||||
)
|
||||
|
||||
def init_verify_data_js(self):
|
||||
js_path = os.path.join(self.js_path, "modules.js")
|
||||
return execjs.compile(
|
||||
open(js_path, encoding="gbk", errors="ignore").read(),
|
||||
cwd=self.node_modules_path,
|
||||
)
|
||||
|
||||
def init_user_encrypt_js(self):
|
||||
js_path = os.path.join(self.js_path, "user_encrypt.js")
|
||||
return execjs.compile(
|
||||
open(js_path, encoding="gbk", errors="ignore").read(),
|
||||
cwd=self.node_modules_path,
|
||||
)
|
||||
|
||||
def init_h5st_js(self):
|
||||
js_path = os.path.join(self.js_path, "h5st-5.0.js")
|
||||
return execjs.compile(
|
||||
open(js_path, encoding="gbk", errors="ignore").read(),
|
||||
cwd=self.node_modules_path,
|
||||
)
|
||||
|
||||
def request_jd_risk_token_id(self):
|
||||
response = self.session.get(
|
||||
"https://payrisk.jd.com/m.html", cookies=self.cookies, headers=self.headers
|
||||
)
|
||||
return response.text
|
||||
|
||||
def build_jcapsid_data(self):
|
||||
return self.user_ctw.call(
|
||||
"init", self.phone, self.fp, self.jd_risk_token_id, self.s_token
|
||||
)
|
||||
|
||||
def request_jcapsid(self):
|
||||
url = "https://plogin.m.jd.com/cgi-bin/mm/jcapsid"
|
||||
data = self.build_jcapsid_data()
|
||||
response = self.session.post(url, headers=self.headers, data=data)
|
||||
print(f"request_jcapsid返回:{response.text}")
|
||||
return response.json()
|
||||
|
||||
def request_new_login_entrance(self):
|
||||
url = "https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance"
|
||||
params = {
|
||||
"lang": "chs",
|
||||
"returnurl": "https://my.m.jd.com/",
|
||||
"risk_jd\\[eid\\]": self.eid,
|
||||
"risk_jd\\[fp\\]": self.fp,
|
||||
}
|
||||
response = self.session.get(
|
||||
url, headers=self.headers, cookies=self.cookies, params=params
|
||||
)
|
||||
print(response.text)
|
||||
return response.json()
|
||||
|
||||
def get_jcap_sid(self):
|
||||
jcapsid_res = self.request_jcapsid()
|
||||
self.jcap_sid = jcapsid_res["jcap_sid"]
|
||||
|
||||
def get_login_entrance(self):
|
||||
login_entrance_res = self.request_new_login_entrance()
|
||||
self.s_token = login_entrance_res["s_token"]
|
||||
self.rsa_modulus = login_entrance_res["rsa_modulus"]
|
||||
|
||||
def build_fp_data(self):
|
||||
return self.slide_ctw.call("getFp", self.phone, self.jcap_sid)
|
||||
|
||||
def request_data_fp(self):
|
||||
data = self.build_fp_data()
|
||||
url = "https://jcap.m.jd.com/cgi-bin/api/fp"
|
||||
response = self.session.post(url, headers=self.headers, data=data)
|
||||
print(response.text)
|
||||
return response.json()
|
||||
|
||||
def get_api_params(self):
|
||||
fp_res = self.request_data_fp()
|
||||
self.api_st = fp_res["st"]
|
||||
self.api_fp = fp_res["fp"]
|
||||
|
||||
def build_captcha_data(self):
|
||||
return self.slide_ctw.call("getImage", self.phone, self.jcap_sid, self.api_st)
|
||||
|
||||
def request_captcha(self):
|
||||
url = "https://jcap.m.jd.com/cgi-bin/api/check"
|
||||
data = self.build_captcha_data()
|
||||
response = requests.post(url, headers=self.headers, data=data)
|
||||
print(response.text)
|
||||
return response.json()
|
||||
|
||||
def set_img(self, captcha_res):
|
||||
self.img = captcha_res["img"]
|
||||
img_json = json.loads(captcha_res["img"])
|
||||
self.api_st = captcha_res["st"]
|
||||
self.img1 = img_json["b1"]
|
||||
self.img2 = img_json["b2"]
|
||||
|
||||
def get_captcha(self):
|
||||
captcha_res = self.request_captcha()
|
||||
if captcha_res.get("vt"):
|
||||
self.vt = captcha_res["vt"]
|
||||
self.api_st = captcha_res["st"]
|
||||
return True
|
||||
self.set_img(captcha_res)
|
||||
return False
|
||||
|
||||
def verify_captcha(self):
|
||||
bg = self.img1.replace("data:image/jpg;base64,", "")
|
||||
background = base64.b64decode(bg)
|
||||
fg = self.img2.replace("data:image/png;base64,", "")
|
||||
target = base64.b64decode(fg)
|
||||
result = self.ocr.slide_match(target, background, simple_target=True)
|
||||
x = round(result["target"][0] * (290 / 275))
|
||||
url = "https://jcap.m.jd.com/cgi-bin/api/check"
|
||||
data = self.slide_ctw.call(
|
||||
"verify", self.phone, self.jcap_sid, self.img, self.api_st, x
|
||||
)
|
||||
response = self.session.post(url, headers=self.headers, data=data)
|
||||
print(response.text)
|
||||
return response.json()
|
||||
|
||||
def check_captcha(self):
|
||||
captcha_res = self.verify_captcha()
|
||||
if captcha_res.get("vt"):
|
||||
self.vt = captcha_res["vt"]
|
||||
self.api_st = captcha_res["st"]
|
||||
return True
|
||||
self.set_img(captcha_res)
|
||||
return False
|
||||
|
||||
def build_send_code_data(self):
|
||||
return self.user_ctw.call(
|
||||
"sendMsg",
|
||||
self.phone,
|
||||
self.vt,
|
||||
self.jd_risk_token_id,
|
||||
self.s_token,
|
||||
self.rsa_modulus,
|
||||
)
|
||||
|
||||
def send_code(self):
|
||||
url = "https://plogin.m.jd.com/cgi-bin/mm/dosendlogincode"
|
||||
data = self.build_send_code_data()
|
||||
response = self.session.post(url, headers=self.headers, data=data)
|
||||
print(response.text)
|
||||
self.ck = self.get_cookie()
|
||||
return response.json()
|
||||
|
||||
def get_h5st(self, code):
|
||||
return self.h5st_ctw.call("getH5st", code, self.s_token)
|
||||
|
||||
def build_verify_data(self, phone_, code, s_token, jd_risk_token_id, rsa_modulus):
|
||||
h5st = self.get_h5st(code)
|
||||
return self.user_ctw.call(
|
||||
"login", phone_, code, s_token, h5st, jd_risk_token_id, rsa_modulus
|
||||
)
|
||||
|
||||
def get_cookie(self):
|
||||
cookie_dict = self.session.cookies.get_dict()
|
||||
return "; ".join([f"{key}={value}" for key, value in cookie_dict.items()]) + ";"
|
||||
|
||||
def request_sms_login(self, ck, code, s_token, jd_risk_token_id, rsa_modulus):
|
||||
url = "https://plogin.m.jd.com/cgi-bin/mm/dosmslogin"
|
||||
h5st = self.get_h5st(code)
|
||||
data = self.user_ctw.call(
|
||||
"login", phone, code, s_token, h5st, jd_risk_token_id, rsa_modulus
|
||||
)
|
||||
self.headers["cookie"] = ck
|
||||
response = requests.post(url, headers=self.headers, data=data)
|
||||
print(response.text)
|
||||
self.ck = self.get_cookie()
|
||||
return response.json()
|
||||
|
||||
def get_jd_risk_token_id(self):
|
||||
self.jd_risk_token_id = (
|
||||
self.request_jd_risk_token_id()
|
||||
.split("var jd_risk_token_id = ")[1]
|
||||
.strip(";")
|
||||
.strip("'")
|
||||
)
|
||||
|
||||
def get_code_res(self):
|
||||
send_res = self.send_code()
|
||||
if send_res.get("err_code") == 0:
|
||||
return 100, {
|
||||
"ck": self.ck,
|
||||
"s_token": self.s_token,
|
||||
"jd_risk_token_id": self.jd_risk_token_id,
|
||||
"rsa_modulus": self.rsa_modulus,
|
||||
}
|
||||
else:
|
||||
return 101, {
|
||||
"ck": "",
|
||||
"s_token": "",
|
||||
"jd_risk_token_id": "",
|
||||
"rsa_modulus": "",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def my_response(status_code, data):
|
||||
return {"code": status_code, "data": data, "msg": "请求成功"}
|
||||
|
||||
def run_get_ck(self, ck, code, s_token, jd_risk_token_id, rsa_modulus):
|
||||
login_res = self.request_sms_login(
|
||||
ck, code, s_token, jd_risk_token_id, rsa_modulus
|
||||
)
|
||||
if login_res.get("err_code") == 0:
|
||||
data = {"ck": self.ck}
|
||||
return self.my_response(status_code=100, data=data)
|
||||
else:
|
||||
data = {"ck": ""}
|
||||
return self.my_response(status_code=101, data=data)
|
||||
|
||||
def run_send_code(self):
|
||||
try:
|
||||
# 获取risk_token_id
|
||||
self.get_jd_risk_token_id()
|
||||
# 获取登录所需初始sid
|
||||
self.get_login_entrance()
|
||||
self.get_jcap_sid()
|
||||
# 获取验证码
|
||||
self.get_api_params()
|
||||
captcha_status = self.get_captcha()
|
||||
|
||||
if not captcha_status:
|
||||
print("出现滑块验证")
|
||||
# 验证验证码
|
||||
check_status = self.check_captcha()
|
||||
if check_status:
|
||||
print("滑块验证通过,发送验证码")
|
||||
status_code, data = self.get_code_res()
|
||||
return self.my_response(status_code, data)
|
||||
return None
|
||||
else:
|
||||
print("没有出现滑块验证,直接发送验证码")
|
||||
status_code, data = self.get_code_res()
|
||||
return self.my_response(status_code, data)
|
||||
except:
|
||||
return self.my_response(status_code=111, data={})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
phone_ = "13071534209"
|
||||
res = LoginSpider(
|
||||
phone_=phone_,
|
||||
).run_send_code()
|
||||
print(res)
|
||||
|
||||
res = LoginSpider(
|
||||
phone_=phone_,
|
||||
).run_get_ck(
|
||||
ck="jcap_dvzw_fp=7Hq5_QuYK92sDlclFxhJ8m28JaYU1hDYJ-U-gC59LmJRPuo3ERCbiMGzn5vdc3WcTX0ndWGFZNOs3BDenMr7lw==; guid=a5bb2dc650ab23a2cf5cb58afef19b3f35adba9613421b4cba79aa59ea891803; lang=chs; lsid=503346699194megeguncgrf6gvwb9tdujp2md5p27pkt1737276375048; lstoken=vvwcnbi4;",
|
||||
code="315572",
|
||||
s_token="vvwcnbi4",
|
||||
jd_risk_token_id="SH6IDCXBUWZNAV3SJPRGIXLVNHGLZR6AYHJ3244TGU5Z35SFJU76TAQ3OMNTXE7XA3SBRMPVTZJVW",
|
||||
rsa_modulus="B03744DE9EAB28F6FA6B9C8FB1873CF57D42A2B6D382B79B276C2079A42B24C11D641EA642CF62485A632AE244DE6DE05A92A20EEFE8B6C7743F09FCE0BF78E6D614C115CDAEC2F4825F82E06770A2599D69BBADBE678DD25F2E5B9E2D0E3E15BEB749B436860872D30676794D3C3E8C37B71372DE52F223917FA730EC21F047",
|
||||
)
|
||||
print(res)
|
||||
193
apps/jd/services/utils.py
Normal file
193
apps/jd/services/utils.py
Normal file
@@ -0,0 +1,193 @@
|
||||
import ctypes
|
||||
import hashlib
|
||||
import json
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from curl_cffi import requests
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
def r(data_string):
|
||||
def int_overflow(val):
|
||||
maxint = 2147483647
|
||||
if not -maxint - 1 <= val <= maxint:
|
||||
val = (val + (maxint + 1)) % (2 * (maxint + 1)) - maxint - 1
|
||||
return val
|
||||
|
||||
def unsigned_right_shitf(n, i):
|
||||
# 数字小于0,则转为32位无符号uint
|
||||
if n < 0:
|
||||
n = ctypes.c_uint32(n).value
|
||||
# 正常位移位数是为正数,但是为了兼容js之类的,负数就右移变成左移好了
|
||||
if i < 0:
|
||||
return -int_overflow(n << abs(i))
|
||||
# print(n)
|
||||
return int_overflow(n >> i)
|
||||
|
||||
char_list = []
|
||||
aae = [
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"y",
|
||||
"z",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"+",
|
||||
"/",
|
||||
]
|
||||
b_arr = data_string.encode("utf-8")
|
||||
for i in range(0, len(b_arr), 3):
|
||||
b_arr2 = [None for i in range(4)]
|
||||
b2 = 0
|
||||
for i2 in range(0, 3):
|
||||
i3 = i + i2
|
||||
if i3 <= len(b_arr) - 1:
|
||||
b_arr2[i2] = b2 | unsigned_right_shitf(
|
||||
(b_arr[i3] & 255), ((i2 * 2) + 2)
|
||||
)
|
||||
b2 = unsigned_right_shitf(
|
||||
((b_arr[i3] & 255) << (((2 - i2) * 2) + 2)) & 255, 2
|
||||
)
|
||||
else:
|
||||
b_arr2[i2] = b2
|
||||
b2 = 64
|
||||
b_arr2[3] = b2
|
||||
for i4 in range(4):
|
||||
if b_arr2[i4] <= 63:
|
||||
char_list.append(aae[b_arr2[i4]])
|
||||
else:
|
||||
char_list.append("=")
|
||||
|
||||
return "".join(char_list)
|
||||
|
||||
|
||||
def encode_cipher(cipher_dict):
|
||||
for k, v in cipher_dict.items():
|
||||
cipher_dict[k] = r(v)
|
||||
|
||||
|
||||
def gen_cipher_ep(uuid, ts):
|
||||
cipher_dict = {
|
||||
"d_model": "SM-N9760",
|
||||
"wifiBssid": "unknown",
|
||||
"osVersion": "9",
|
||||
"d_brand": "samsung",
|
||||
"screen": "960*540",
|
||||
"uuid": uuid,
|
||||
"aid": uuid,
|
||||
}
|
||||
encode_cipher(cipher_dict)
|
||||
data_dict = {
|
||||
"hdid": "JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw=",
|
||||
"ts": ts,
|
||||
"ridx": -1,
|
||||
"cipher": cipher_dict,
|
||||
"ciphertype": 5,
|
||||
"version": "1.2.0",
|
||||
"appname": "com.jingdong.app.mall",
|
||||
}
|
||||
ep = json.dumps(data_dict, separators=(",", ":"))
|
||||
|
||||
return ep
|
||||
|
||||
|
||||
def get_pay_sign(order_id, face_price):
|
||||
app_id = "jd_android_app4"
|
||||
pay_app_key = "e53jfgRgd7Hk"
|
||||
input_str = f"{app_id};{order_id};37;{face_price};{pay_app_key}"
|
||||
# 获取 MD5 实例
|
||||
input_bytes = input_str.encode("GBK")
|
||||
md5_hash = hashlib.md5()
|
||||
md5_hash.update(input_bytes)
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=15))
|
||||
def get_sign(func, body, uuid_, version):
|
||||
data = {
|
||||
"func": func,
|
||||
"body": body,
|
||||
"uid": uuid_,
|
||||
"platform": "android",
|
||||
"version": version,
|
||||
}
|
||||
logger.info(f"请求参数:{data}")
|
||||
response_jd = requests.post(
|
||||
url="http://unidbg-boot-server:9999/api/jd/encrypt",
|
||||
json=data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
logger.info(f"请求结果:{response_jd.text}")
|
||||
res = response_jd.json()
|
||||
params = parse_qs(res["data"])
|
||||
formatted_params = {key: value[0] for key, value in params.items()}
|
||||
return formatted_params
|
||||
|
||||
|
||||
def my_json(code, data, msg):
|
||||
"""返回标准格式的JSON响应"""
|
||||
# 处理枚举类型
|
||||
if hasattr(code, "value"):
|
||||
code = code.value
|
||||
return {"code": code, "data": data, "msg": msg}
|
||||
0
apps/shared/proxy_pool/__init__.py
Normal file
0
apps/shared/proxy_pool/__init__.py
Normal file
442
apps/shared/proxy_pool/proxy_pool.py
Normal file
442
apps/shared/proxy_pool/proxy_pool.py
Normal file
@@ -0,0 +1,442 @@
|
||||
from collections import Counter
|
||||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Dict, Tuple
|
||||
|
||||
from curl_cffi import ProxySpec, requests
|
||||
|
||||
from core.config import ProxyPoolType, settings
|
||||
from observability.logging import get_logger_with_trace
|
||||
|
||||
# from app.config.config import Config
|
||||
# from app.logging.logger import get_logger
|
||||
# from app.proxy_pool.enums import ProxyPoolType
|
||||
|
||||
# 禁用 HTTPS 请求的警告
|
||||
logger = get_logger_with_trace(__name__)
|
||||
|
||||
|
||||
class BaseProxyPool(ABC):
|
||||
"""代理池抽象基类"""
|
||||
|
||||
def __init__(self):
|
||||
self.proxy_timeout = 3 # 代理超时时间,单位秒
|
||||
self.test_url = "https://www.baidu.com"
|
||||
self.max_retries = 1 # 最大重试次数
|
||||
self.lock = threading.Lock() # 添加线程锁以确保并发安全
|
||||
|
||||
@abstractmethod
|
||||
def get_proxy(self, *args, **kwargs) -> Optional[str]:
|
||||
"""获取代理的抽象方法"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def release_proxy(self, *args, **kwargs):
|
||||
"""释放代理的抽象方法"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _get_new_proxy(self) -> Optional[str]:
|
||||
"""获取新的代理的抽象方法"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mark_proxy_invalid(self, proxy: str):
|
||||
"""标记代理为无效的抽象方法"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remove_invalid_proxy(self, proxy: str):
|
||||
"""删除指定的无效代理的抽象方法"""
|
||||
pass
|
||||
|
||||
def _validate_proxy_with_auth(self, proxy: str) -> bool:
|
||||
"""验证带认证信息的代理是否可用
|
||||
Args:
|
||||
proxy: 代理地址,可能是纯IP地址或带认证信息的完整代理地址
|
||||
Returns:
|
||||
bool: 代理是否可用
|
||||
"""
|
||||
# 检查是否已经是带认证信息的代理地址
|
||||
if proxy.startswith("http://") and "@" in proxy:
|
||||
proxyMeta = ProxySpec(all=proxy)
|
||||
else:
|
||||
# 如果是纯IP地址,添加认证信息
|
||||
proxyMeta: ProxySpec = ProxySpec(all="http://%(user)s:%(pass)s@%(host)s" % {
|
||||
"host": proxy,
|
||||
"user": settings.proxy_username,
|
||||
"pass": settings.proxy_password,
|
||||
})
|
||||
|
||||
for attempt in range(self.max_retries):
|
||||
try:
|
||||
response = requests.get(
|
||||
self.test_url,
|
||||
proxies=proxyMeta,
|
||||
timeout=self.proxy_timeout,
|
||||
verify=False,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
logger.warning(
|
||||
f"带认证的代理验证失败,状态码: {response.status_code},重试次数: {attempt + 1},代理: {proxyMeta}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"带认证的代理验证出错: {str(e)},代理: {proxyMeta},重试次数: {attempt + 1}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class DefaultProxyPool(BaseProxyPool):
|
||||
"""默认代理池实现"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.order_proxy_map: Dict[str, str] = {} # 订单ID -> 代理地址
|
||||
self.proxy_order_count_map: Dict[str, int] = {} # 代理地址 -> 使用计数
|
||||
self.max_orders_per_proxy = 5 # 一个代理最多可在5个订单中使用
|
||||
|
||||
def get_proxy(self, order_id: str = "") -> Optional[str]:
|
||||
"""获取指定订单的代理
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
Returns:
|
||||
代理地址,格式为 http://user:pass@ip:port,如果获取失败则返回None
|
||||
"""
|
||||
with self.lock: # 使用线程锁确保并发安全
|
||||
# 检查是否已有分配的代理
|
||||
if order_id in self.order_proxy_map:
|
||||
proxy = self.order_proxy_map[order_id]
|
||||
if self._validate_proxy_with_auth(proxy):
|
||||
return proxy
|
||||
else:
|
||||
# 代理无效,清理相关映射
|
||||
self._cleanup_proxy_mapping(proxy)
|
||||
del self.order_proxy_map[order_id]
|
||||
|
||||
# 尝试复用现有代理(使用次数未达上限)
|
||||
for proxy, count in self.proxy_order_count_map.items():
|
||||
if count < self.max_orders_per_proxy and self._validate_proxy_with_auth(proxy):
|
||||
# 复用此代理
|
||||
self.order_proxy_map[order_id] = proxy
|
||||
self.proxy_order_count_map[proxy] = count + 1
|
||||
logger.info(f"订单 {order_id} 复用代理 {proxy},当前使用次数: {count + 1}")
|
||||
return proxy
|
||||
|
||||
# 获取新代理
|
||||
try:
|
||||
proxy = self._get_new_proxy()
|
||||
if proxy is not None:
|
||||
self.order_proxy_map[order_id] = proxy
|
||||
self.proxy_order_count_map[proxy] = 1
|
||||
logger.info(f"订单 {order_id} 获取新代理 {proxy}")
|
||||
return proxy
|
||||
except Exception as e:
|
||||
logger.error(f"获取代理失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def release_proxy(self, order_id: str):
|
||||
"""释放指定订单的代理
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
"""
|
||||
with self.lock: # 使用线程锁确保并发安全
|
||||
if order_id in self.order_proxy_map:
|
||||
proxy = self.order_proxy_map[order_id]
|
||||
del self.order_proxy_map[order_id]
|
||||
|
||||
# 减少代理使用计数
|
||||
if proxy in self.proxy_order_count_map:
|
||||
self.proxy_order_count_map[proxy] -= 1
|
||||
if self.proxy_order_count_map[proxy] <= 0:
|
||||
del self.proxy_order_count_map[proxy]
|
||||
logger.info(f"代理 {proxy} 使用计数归零,已从计数映射中删除")
|
||||
else:
|
||||
logger.info(f"订单 {order_id} 释放代理 {proxy},剩余使用次数: {self.proxy_order_count_map[proxy]}")
|
||||
|
||||
logger.info(f"订单 {order_id} 已释放代理 {proxy}")
|
||||
|
||||
def _cleanup_proxy_mapping(self, proxy: str):
|
||||
"""清理代理相关的所有映射
|
||||
Args:
|
||||
proxy: 要清理的代理地址
|
||||
"""
|
||||
if proxy in self.proxy_order_count_map:
|
||||
del self.proxy_order_count_map[proxy]
|
||||
logger.info(f"已清理代理 {proxy} 的计数映射")
|
||||
|
||||
def mark_proxy_invalid(self, proxy: str):
|
||||
"""标记代理为无效
|
||||
Args:
|
||||
proxy: 要标记为无效的代理地址
|
||||
"""
|
||||
self.remove_invalid_proxy(proxy)
|
||||
|
||||
def remove_invalid_proxy(self, proxy: str):
|
||||
"""删除指定的无效代理
|
||||
Args:
|
||||
proxy: 要删除的代理地址
|
||||
"""
|
||||
with self.lock:
|
||||
# 查找并删除使用该代理的所有订单映射
|
||||
orders_to_remove = []
|
||||
for order_id, proxy_addr in self.order_proxy_map.items():
|
||||
if proxy_addr == proxy:
|
||||
orders_to_remove.append(order_id)
|
||||
|
||||
# 删除找到的订单映射
|
||||
for order_id in orders_to_remove:
|
||||
del self.order_proxy_map[order_id]
|
||||
logger.info(f"已删除订单 {order_id} 的无效代理 {proxy}")
|
||||
|
||||
# 清理代理的计数映射
|
||||
self._cleanup_proxy_mapping(proxy)
|
||||
|
||||
if not orders_to_remove:
|
||||
logger.warning(f"未找到使用代理 {proxy} 的订单,可能已被删除")
|
||||
|
||||
def _get_new_proxy(self) -> Optional[str]:
|
||||
"""获取新的代理,最多尝试3次
|
||||
Returns:
|
||||
代理地址,格式为 http://user:pass@ip:port
|
||||
Raises:
|
||||
Exception: 连续3次获取代理失败时抛出异常
|
||||
"""
|
||||
max_attempts = 2 # 最大尝试次数
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
res = requests.get(
|
||||
settings.proxy_url,
|
||||
timeout=self.proxy_timeout,
|
||||
)
|
||||
ip = res.text.strip()
|
||||
if ip and self._validate_proxy_with_auth(ip):
|
||||
# 构建带认证信息的代理地址
|
||||
proxyMeta = "http://%(user)s:%(pass)s@%(host)s" % {
|
||||
"host": ip,
|
||||
"user": settings.proxy_username,
|
||||
"pass": settings.proxy_password,
|
||||
}
|
||||
return proxyMeta
|
||||
logger.warning(
|
||||
f"获取代理失败或代理验证失败,尝试次数: {attempt + 1}/{max_attempts}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"获取代理出错: {str(e)},尝试次数: {attempt + 1}/{max_attempts}"
|
||||
)
|
||||
|
||||
# 所有尝试都失败,抛出异常
|
||||
raise Exception(f"连续{max_attempts}次获取代理失败")
|
||||
|
||||
def get_all_proxies(self) -> Dict[str, str]:
|
||||
"""获取所有订单的代理映射
|
||||
Returns:
|
||||
Dict[str, str]: 订单ID到代理地址的映射
|
||||
"""
|
||||
with self.lock: # 使用线程锁确保并发安全
|
||||
return self.order_proxy_map.copy()
|
||||
|
||||
def get_proxy_usage_stats(self) -> Dict[str, int]:
|
||||
"""获取代理使用统计信息
|
||||
Returns:
|
||||
Dict[str, int]: 代理地址到使用次数的映射
|
||||
"""
|
||||
with self.lock: # 使用线程锁确保并发安全
|
||||
return self.proxy_order_count_map.copy()
|
||||
|
||||
def set_max_orders_per_proxy(self, max_orders: int):
|
||||
"""设置一个代理最多可使用的订单数
|
||||
Args:
|
||||
max_orders: 最大订单数量
|
||||
"""
|
||||
with self.lock:
|
||||
self.max_orders_per_proxy = max_orders
|
||||
logger.info(f"代理最大使用次数已设置为: {max_orders}")
|
||||
|
||||
|
||||
class ExpiringProxyPool(BaseProxyPool):
|
||||
"""带有效期的代理池实现"""
|
||||
|
||||
def __init__(self, expire_time: int = 60):
|
||||
super().__init__()
|
||||
self.current_proxy: Optional[Tuple[str, float]] = None # 当前代理及其过期时间
|
||||
self.proxy_order_count_map = Counter() # 代理使用计数
|
||||
self.expire_time = expire_time # 代理有效期
|
||||
self.invalid_proxies: set = set() # 存储无效的代理
|
||||
self.max_orders_per_proxy = 5 # 一个代理最多可在5个订单中使用
|
||||
|
||||
def get_proxy(self, order_id: str = "") -> Optional[str]:
|
||||
"""获取指定订单的代理
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
Returns:
|
||||
代理地址,格式为 http://user:pass@ip:port,如果获取失败则返回None
|
||||
"""
|
||||
with self.lock: # 使用线程锁确保并发安全
|
||||
# 检查当前代理是否有效且未过期
|
||||
if self.current_proxy:
|
||||
proxy, expire_time = self.current_proxy
|
||||
# 如果代理未过期且不在无效列表中,则返回
|
||||
if (
|
||||
time.time() < expire_time
|
||||
and proxy not in self.invalid_proxies
|
||||
and self._validate_proxy_with_auth(proxy)
|
||||
and self.proxy_order_count_map[proxy] < self.max_orders_per_proxy
|
||||
):
|
||||
self.proxy_order_count_map[proxy] += 1
|
||||
return proxy
|
||||
|
||||
# 获取新代理
|
||||
try:
|
||||
proxy = self._get_new_proxy()
|
||||
if proxy:
|
||||
# 设置代理过期时间
|
||||
expire_time = time.time() + self.expire_time
|
||||
self.current_proxy = (proxy, expire_time)
|
||||
return proxy
|
||||
except Exception as e:
|
||||
logger.error(f"获取代理失败: {str(e)}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def release_proxy(self, *args, **kwargs):
|
||||
"""释放代理(此实现不需要)"""
|
||||
pass
|
||||
|
||||
def _get_new_proxy(self) -> Optional[str]:
|
||||
"""获取新的代理,最多尝试3次
|
||||
Returns:
|
||||
代理地址,格式为 http://user:pass@ip:port
|
||||
Raises:
|
||||
Exception: 连续3次获取代理失败时抛出异常
|
||||
"""
|
||||
max_attempts = 2 # 最大尝试次数
|
||||
logger.info(
|
||||
f"获取代理{settings.proxy_url} {settings.proxy_username} {settings.proxy_password}"
|
||||
)
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
res = requests.get(
|
||||
settings.proxy_url,
|
||||
timeout=self.proxy_timeout,
|
||||
)
|
||||
ip = res.text.strip()
|
||||
logger.info(f"获取代理:{ip}")
|
||||
if ip and self._validate_proxy_with_auth(ip):
|
||||
# 构建带认证信息的代理地址
|
||||
proxyMeta = "http://%(user)s:%(pass)s@%(host)s" % {
|
||||
"host": ip,
|
||||
"user": settings.proxy_username,
|
||||
"pass": settings.proxy_password,
|
||||
}
|
||||
# 检查新获取的代理是否在无效列表中
|
||||
if proxyMeta not in self.invalid_proxies:
|
||||
return proxyMeta
|
||||
logger.warning(
|
||||
f"获取的代理 {proxyMeta} 在无效列表中,继续尝试获取新代理"
|
||||
)
|
||||
logger.warning(
|
||||
f"获取代理失败或代理验证失败,尝试次数: {attempt + 1}/{max_attempts}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"获取代理出错: {str(e)},尝试次数: {attempt + 1}/{max_attempts}"
|
||||
)
|
||||
|
||||
# 所有尝试都失败,抛出异常
|
||||
raise Exception(f"连续{max_attempts}次获取代理失败")
|
||||
|
||||
def mark_proxy_invalid(self, proxy: str):
|
||||
"""标记代理为无效
|
||||
Args:
|
||||
proxy: 要标记为无效的代理地址
|
||||
"""
|
||||
with self.lock:
|
||||
# 添加到无效代理列表
|
||||
self.invalid_proxies.add(proxy)
|
||||
# 如果当前代理被标记为无效,立即清除
|
||||
if self.current_proxy and self.current_proxy[0] == proxy:
|
||||
self.current_proxy = None
|
||||
logger.info(f"代理 {proxy} 已被标记为无效")
|
||||
|
||||
def clear_invalid_proxies(self):
|
||||
"""清除无效代理列表
|
||||
用于定期清理无效代理列表,避免内存占用过大
|
||||
"""
|
||||
with self.lock:
|
||||
self.invalid_proxies.clear()
|
||||
logger.info("无效代理列表已清除")
|
||||
|
||||
def remove_invalid_proxy(self, proxy: str):
|
||||
"""删除指定的无效代理
|
||||
Args:
|
||||
proxy: 要删除的代理地址
|
||||
"""
|
||||
with self.lock:
|
||||
# 从无效代理列表中删除该代理(如果存在)
|
||||
if proxy in self.invalid_proxies:
|
||||
self.invalid_proxies.remove(proxy)
|
||||
logger.info(f"已从无效代理列表中删除代理 {proxy}")
|
||||
|
||||
# 如果当前代理就是要删除的代理,也清除当前代理
|
||||
if self.current_proxy and self.current_proxy[0] == proxy:
|
||||
self.current_proxy = None
|
||||
logger.info(f"已清除当前无效代理 {proxy}")
|
||||
|
||||
def set_expire_time(self, seconds: int):
|
||||
"""设置代理有效期
|
||||
Args:
|
||||
seconds: 代理有效期,单位秒
|
||||
"""
|
||||
with self.lock:
|
||||
self.expire_time = seconds
|
||||
|
||||
|
||||
class ProxyPoolFactory:
|
||||
"""代理池工厂类"""
|
||||
|
||||
_instances: Dict[ProxyPoolType, BaseProxyPool] = {}
|
||||
_lock = threading.Lock()
|
||||
|
||||
@classmethod
|
||||
def get_proxy_pool(
|
||||
cls, pool_type: ProxyPoolType = ProxyPoolType.DEFAULT, **kwargs
|
||||
) -> BaseProxyPool:
|
||||
"""获取代理池实例
|
||||
Args:
|
||||
pool_type: 代理池类型
|
||||
**kwargs: 代理池初始化参数
|
||||
Returns:
|
||||
BaseProxyPool: 代理池实例
|
||||
"""
|
||||
with cls._lock:
|
||||
if pool_type not in cls._instances:
|
||||
if pool_type == ProxyPoolType.DEFAULT:
|
||||
cls._instances[pool_type] = DefaultProxyPool()
|
||||
elif pool_type == ProxyPoolType.EXPIRING:
|
||||
expire_time = kwargs.get("expire_time", 60)
|
||||
cls._instances[pool_type] = ExpiringProxyPool(
|
||||
expire_time=expire_time
|
||||
)
|
||||
return cls._instances[pool_type]
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 获取默认代理池
|
||||
# default_pool = ProxyPoolFactory.get_proxy_pool()
|
||||
# print(default_pool.get_proxy("test_order"))
|
||||
|
||||
# 获取带有效期的代理池
|
||||
for i in range(10):
|
||||
expiring_pool = ProxyPoolFactory.get_proxy_pool(
|
||||
ProxyPoolType.EXPIRING, expire_time=60
|
||||
)
|
||||
proxy = expiring_pool.get_proxy(order_id="test_order")
|
||||
print(proxy)
|
||||
time.sleep(2)
|
||||
@@ -3,11 +3,19 @@ Core configuration management using Pydantic Settings.
|
||||
All configuration loaded from environment variables with validation.
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Literal
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class ProxyPoolType(StrEnum):
|
||||
"""代理池类型枚举"""
|
||||
|
||||
DEFAULT = "default" # 默认代理池
|
||||
EXPIRING = "expiring" # 带有效期的代理池
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""
|
||||
Application settings with environment variable support.
|
||||
@@ -26,16 +34,14 @@ class Settings(BaseSettings):
|
||||
# Application Settings
|
||||
app_name: str = Field(default="kami_spider", description="Application name")
|
||||
environment: Literal["development", "staging", "production"] = Field(
|
||||
default="development",
|
||||
description="Runtime environment"
|
||||
default="development", description="Runtime environment"
|
||||
)
|
||||
debug: bool = Field(default=False, description="Debug mode")
|
||||
host: str = Field(default="0.0.0.0", description="Server host")
|
||||
port: int = Field(default=8000, description="Server port")
|
||||
workers: int = Field(default=1, description="Number of worker processes")
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
|
||||
default="INFO",
|
||||
description="Logging level"
|
||||
default="INFO", description="Logging level"
|
||||
)
|
||||
|
||||
# Database Settings
|
||||
@@ -45,9 +51,15 @@ class Settings(BaseSettings):
|
||||
db_user: str = Field(default="root", description="Database user")
|
||||
db_password: str = Field(default="", description="Database password")
|
||||
db_pool_size: int = Field(default=10, description="Database connection pool size")
|
||||
db_max_overflow: int = Field(default=20, description="Database max overflow connections")
|
||||
db_pool_recycle: int = Field(default=3600, description="Database pool recycle time in seconds")
|
||||
db_pool_pre_ping: bool = Field(default=True, description="Test connections before using")
|
||||
db_max_overflow: int = Field(
|
||||
default=20, description="Database max overflow connections"
|
||||
)
|
||||
db_pool_recycle: int = Field(
|
||||
default=3600, description="Database pool recycle time in seconds"
|
||||
)
|
||||
db_pool_pre_ping: bool = Field(
|
||||
default=True, description="Test connections before using"
|
||||
)
|
||||
db_echo: bool = Field(default=False, description="Echo SQL statements")
|
||||
|
||||
# Redis Settings
|
||||
@@ -55,39 +67,59 @@ class Settings(BaseSettings):
|
||||
redis_port: int = Field(default=6379, description="Redis port")
|
||||
redis_db: int = Field(default=0, description="Redis database number")
|
||||
redis_password: str = Field(default="", description="Redis password")
|
||||
redis_max_connections: int = Field(default=50, description="Redis connection pool max connections")
|
||||
redis_decode_responses: bool = Field(default=True, description="Decode Redis responses to strings")
|
||||
redis_max_connections: int = Field(
|
||||
default=50, description="Redis connection pool max connections"
|
||||
)
|
||||
redis_decode_responses: bool = Field(
|
||||
default=True, description="Decode Redis responses to strings"
|
||||
)
|
||||
|
||||
# OpenTelemetry Settings
|
||||
otel_enabled: bool = Field(default=True, description="Enable OpenTelemetry")
|
||||
otel_service_name: str = Field(default="kami_spider", description="Service name for traces")
|
||||
otel_service_name: str = Field(
|
||||
default="kami_spider", description="Service name for traces"
|
||||
)
|
||||
otel_exporter_endpoint: str = Field(
|
||||
default="38.38.251.113:31547",
|
||||
description="OpenTelemetry collector gRPC endpoint"
|
||||
description="OpenTelemetry collector gRPC endpoint",
|
||||
)
|
||||
otel_exporter_insecure: bool = Field(
|
||||
default=True, description="Use insecure gRPC connection"
|
||||
)
|
||||
otel_sample_rate: float = Field(
|
||||
default=1.0, description="Trace sampling rate (0.0 to 1.0)"
|
||||
)
|
||||
otel_exporter_insecure: bool = Field(default=True, description="Use insecure gRPC connection")
|
||||
otel_sample_rate: float = Field(default=1.0, description="Trace sampling rate (0.0 to 1.0)")
|
||||
|
||||
# CORS Settings
|
||||
cors_enabled: bool = Field(default=True, description="Enable CORS")
|
||||
cors_allow_origins: list[str] = Field(
|
||||
default=["*"],
|
||||
description="Allowed CORS origins"
|
||||
default=["*"], description="Allowed CORS origins"
|
||||
)
|
||||
cors_allow_credentials: bool = Field(
|
||||
default=True, description="Allow credentials in CORS"
|
||||
)
|
||||
cors_allow_credentials: bool = Field(default=True, description="Allow credentials in CORS")
|
||||
cors_allow_methods: list[str] = Field(
|
||||
default=["*"],
|
||||
description="Allowed HTTP methods"
|
||||
default=["*"], description="Allowed HTTP methods"
|
||||
)
|
||||
cors_allow_headers: list[str] = Field(
|
||||
default=["*"],
|
||||
description="Allowed HTTP headers"
|
||||
default=["*"], description="Allowed HTTP headers"
|
||||
)
|
||||
|
||||
# 代理设置
|
||||
proxy_enable: bool = Field(default=True, description="是否启用代理")
|
||||
proxy_url: str = Field(
|
||||
default="https://share.proxy.qg.net/get?key=7ASQH2BI&num=1&area=&isp=0&format=txt&seq=\r\n&distinct=false&area=510100",
|
||||
description="代理服务器地址",
|
||||
)
|
||||
proxy_type: ProxyPoolType = Field(
|
||||
default=ProxyPoolType.DEFAULT, description="代理服务器类型"
|
||||
)
|
||||
proxy_username: str = Field(default="7ASQH2BI", description="代理服务器用户名")
|
||||
proxy_password: str = Field(default="34D6652FE7B6", description="代理服务器密码")
|
||||
|
||||
# Security Settings
|
||||
secret_key: str = Field(
|
||||
default="change-me-in-production",
|
||||
description="Secret key for signing tokens"
|
||||
default="change-me-in-production", description="Secret key for signing tokens"
|
||||
)
|
||||
|
||||
@field_validator("workers")
|
||||
|
||||
@@ -21,9 +21,9 @@ class BaseAppException(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.UNKNOWN_ERROR,
|
||||
code: BusinessCode = BusinessCode.UNKNOWN_ERROR,
|
||||
status_code: int = 500,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
self.message = message
|
||||
self.code = code
|
||||
@@ -43,15 +43,10 @@ class ValidationException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.INVALID_INPUT,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
code: BusinessCode = BusinessCode.INVALID_INPUT,
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=400,
|
||||
details=details
|
||||
)
|
||||
super().__init__(message=message, code=code, status_code=400, details=details)
|
||||
|
||||
|
||||
class NotFoundException(BaseAppException):
|
||||
@@ -66,13 +61,13 @@ class NotFoundException(BaseAppException):
|
||||
self,
|
||||
message: str,
|
||||
resource: str = "Resource",
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message or f"{resource} not found",
|
||||
code=BusinessCode.RESOURCE_NOT_FOUND,
|
||||
status_code=404,
|
||||
details=details
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
@@ -87,15 +82,10 @@ class ConflictException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.RESOURCE_CONFLICT,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
code: BusinessCode = BusinessCode.RESOURCE_CONFLICT,
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=409,
|
||||
details=details
|
||||
)
|
||||
super().__init__(message=message, code=code, status_code=409, details=details)
|
||||
|
||||
|
||||
class AlreadyExistsException(BaseAppException):
|
||||
@@ -110,13 +100,13 @@ class AlreadyExistsException(BaseAppException):
|
||||
self,
|
||||
message: str,
|
||||
resource: str = "Resource",
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message or f"{resource} already exists",
|
||||
code=BusinessCode.RESOURCE_ALREADY_EXISTS,
|
||||
status_code=409,
|
||||
details=details
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
@@ -131,15 +121,10 @@ class AuthenticationException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.LOGIN_FAILED,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
code: BusinessCode = BusinessCode.LOGIN_FAILED,
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=401,
|
||||
details=details
|
||||
)
|
||||
super().__init__(message=message, code=code, status_code=401, details=details)
|
||||
|
||||
|
||||
class PermissionException(BaseAppException):
|
||||
@@ -153,15 +138,10 @@ class PermissionException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.INSUFFICIENT_PERMISSIONS,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
code: BusinessCode = BusinessCode.INSUFFICIENT_PERMISSIONS,
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=403,
|
||||
details=details
|
||||
)
|
||||
super().__init__(message=message, code=code, status_code=403, details=details)
|
||||
|
||||
|
||||
class BusinessLogicException(BaseAppException):
|
||||
@@ -175,15 +155,10 @@ class BusinessLogicException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: int = BusinessCode.OPERATION_NOT_ALLOWED,
|
||||
details: Optional[dict[str, Any]] = None
|
||||
code: BusinessCode = BusinessCode.OPERATION_NOT_ALLOWED,
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=400,
|
||||
details=details
|
||||
)
|
||||
super().__init__(message=message, code=code, status_code=400, details=details)
|
||||
|
||||
|
||||
class DatabaseException(BaseAppException):
|
||||
@@ -197,13 +172,13 @@ class DatabaseException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Database error occurred",
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=BusinessCode.DATABASE_ERROR,
|
||||
status_code=503,
|
||||
details=details
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
@@ -218,13 +193,13 @@ class CacheException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Cache service error",
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=BusinessCode.CACHE_ERROR,
|
||||
status_code=503,
|
||||
details=details
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
@@ -239,11 +214,33 @@ class ExternalServiceException(BaseAppException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "External service unavailable",
|
||||
details: Optional[dict[str, Any]] = None
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=BusinessCode.EXTERNAL_SERVICE_ERROR,
|
||||
status_code=502,
|
||||
details=details
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class JDServiceException(BaseAppException):
|
||||
"""
|
||||
Exception for external service errors.
|
||||
|
||||
HTTP Status: 502 Bad Gateway
|
||||
Business Code: 5002
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code: BusinessCode = BusinessCode.NOT_IMPLEMENTED,
|
||||
message: str = "External service unavailable",
|
||||
details: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
code=code,
|
||||
status_code=200,
|
||||
details=details,
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ Provides unified response structure for all API endpoints.
|
||||
from typing import TypeVar, Generic, Optional, Any
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -29,18 +29,9 @@ class ApiResponse(BaseModel, Generic[T]):
|
||||
message: str = Field(description="Human-readable message")
|
||||
data: Optional[T] = Field(default=None, description="Response payload")
|
||||
trace_id: str = Field(description="Request trace ID")
|
||||
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Response timestamp")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": 0,
|
||||
"message": "Success",
|
||||
"data": {"user_id": 12345, "username": "john_doe"},
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
timestamp: datetime = Field(
|
||||
default_factory=datetime.utcnow, description="Response timestamp"
|
||||
)
|
||||
|
||||
|
||||
class PaginationMeta(BaseModel):
|
||||
@@ -66,9 +57,7 @@ class PaginatedData(BaseModel, Generic[T]):
|
||||
|
||||
|
||||
def success(
|
||||
data: Optional[T] = None,
|
||||
message: str = "Success",
|
||||
trace_id: str = ""
|
||||
data: Optional[T] = None, message: str = "Success", trace_id: str = ""
|
||||
) -> ApiResponse[T]:
|
||||
"""
|
||||
Create success response.
|
||||
@@ -85,19 +74,15 @@ def success(
|
||||
return success(data={"user_id": 123}, message="User created")
|
||||
"""
|
||||
return ApiResponse(
|
||||
code=0,
|
||||
code=BusinessCode.SUCCESS,
|
||||
message=message,
|
||||
data=data,
|
||||
trace_id=trace_id,
|
||||
timestamp=datetime.utcnow()
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
def error(
|
||||
code: int,
|
||||
message: str,
|
||||
trace_id: str = ""
|
||||
) -> ApiResponse[None]:
|
||||
def error(code: "BusinessCode", data: Any=None, message: str="", trace_id: str = "") -> ApiResponse[None]:
|
||||
"""
|
||||
Create error response.
|
||||
|
||||
@@ -107,7 +92,7 @@ def error(
|
||||
trace_id: Request trace ID
|
||||
|
||||
Returns:
|
||||
ApiResponse: Error response with data=None
|
||||
ApiResponse: Error response
|
||||
|
||||
Example:
|
||||
return error(code=1001, message="Login failed: Invalid credentials")
|
||||
@@ -115,12 +100,15 @@ def error(
|
||||
if code <= 0:
|
||||
raise ValueError("Error code must be greater than 0")
|
||||
|
||||
if message == "":
|
||||
message = BusinessCode(code).name
|
||||
|
||||
return ApiResponse(
|
||||
code=code,
|
||||
message=message,
|
||||
data=None,
|
||||
data=data,
|
||||
trace_id=trace_id,
|
||||
timestamp=datetime.utcnow()
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
@@ -130,7 +118,7 @@ def paginated(
|
||||
page: int,
|
||||
page_size: int,
|
||||
message: str = "Success",
|
||||
trace_id: str = ""
|
||||
trace_id: str = "",
|
||||
) -> ApiResponse[PaginatedData[T]]:
|
||||
"""
|
||||
Create paginated response.
|
||||
@@ -157,28 +145,22 @@ def paginated(
|
||||
total_pages = (total + page_size - 1) // page_size if page_size > 0 else 0
|
||||
|
||||
pagination_meta = PaginationMeta(
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_pages=total_pages
|
||||
total=total, page=page, page_size=page_size, total_pages=total_pages
|
||||
)
|
||||
|
||||
paginated_data = PaginatedData(
|
||||
items=items,
|
||||
pagination=pagination_meta
|
||||
)
|
||||
paginated_data = PaginatedData(items=items, pagination=pagination_meta)
|
||||
|
||||
return ApiResponse(
|
||||
code=0,
|
||||
message=message,
|
||||
data=paginated_data,
|
||||
trace_id=trace_id,
|
||||
timestamp=datetime.utcnow()
|
||||
timestamp=datetime.utcnow(),
|
||||
)
|
||||
|
||||
|
||||
# Business code constants
|
||||
class BusinessCode:
|
||||
class BusinessCode(IntEnum):
|
||||
"""
|
||||
Business status code definitions.
|
||||
|
||||
@@ -221,10 +203,23 @@ class BusinessCode:
|
||||
DATABASE_ERROR = 5001
|
||||
EXTERNAL_SERVICE_ERROR = 5002
|
||||
CACHE_ERROR = 5003
|
||||
INTERNAL_ERROR = 5004
|
||||
|
||||
# 没有实现
|
||||
NOT_IMPLEMENTED = 5004
|
||||
|
||||
# Unknown Errors (9000-9999)
|
||||
UNKNOWN_ERROR = 9000
|
||||
|
||||
# 京东充值的错误
|
||||
JD_ORDER_FACE_PRICE_ERR = 10001
|
||||
JD_ORDER_NORMAL_ERR = 10002
|
||||
JD_ORDER_CK_ERR = 10003
|
||||
JD_ORDER_TYPE_NOT_SUPPORTED_ERR = 10004
|
||||
JD_ORDER_EXPIRED_ERR = 10005
|
||||
JD_ORDER_STOCK_ERR = 10006
|
||||
JD_ORDER_RISK_ERR = 10007
|
||||
|
||||
|
||||
# Common error response examples for OpenAPI documentation
|
||||
ERROR_RESPONSES = {
|
||||
@@ -237,10 +232,10 @@ ERROR_RESPONSES = {
|
||||
"message": "Validation error: email - field required",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
401: {
|
||||
"description": "Unauthorized - Authentication failed",
|
||||
@@ -251,10 +246,10 @@ ERROR_RESPONSES = {
|
||||
"message": "Login failed: Invalid credentials",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
403: {
|
||||
"description": "Forbidden - Insufficient permissions",
|
||||
@@ -265,10 +260,10 @@ ERROR_RESPONSES = {
|
||||
"message": "Insufficient permissions to perform this action",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
404: {
|
||||
"description": "Not Found - Resource does not exist",
|
||||
@@ -279,10 +274,10 @@ ERROR_RESPONSES = {
|
||||
"message": "User not found",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
409: {
|
||||
"description": "Conflict - Resource already exists or conflicts",
|
||||
@@ -293,10 +288,10 @@ ERROR_RESPONSES = {
|
||||
"message": "User already exists",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
422: {
|
||||
"description": "Unprocessable Entity - Validation error",
|
||||
@@ -307,10 +302,10 @@ ERROR_RESPONSES = {
|
||||
"message": "Validation error: body -> email - value is not a valid email address",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
500: {
|
||||
"description": "Internal Server Error - Unexpected error",
|
||||
@@ -321,10 +316,10 @@ ERROR_RESPONSES = {
|
||||
"message": "An unexpected error occurred",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
502: {
|
||||
"description": "Bad Gateway - External service error",
|
||||
@@ -335,10 +330,10 @@ ERROR_RESPONSES = {
|
||||
"message": "External service unavailable",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
503: {
|
||||
"description": "Service Unavailable - Database or cache error",
|
||||
@@ -349,10 +344,10 @@ ERROR_RESPONSES = {
|
||||
"message": "Database error occurred",
|
||||
"data": None,
|
||||
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
23
main.py
23
main.py
@@ -4,7 +4,7 @@ Bootstraps the application with middleware, routers, and lifecycle handlers.
|
||||
"""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from core.config import settings
|
||||
@@ -16,8 +16,10 @@ from observability.logging import setup_logging, get_logger
|
||||
from middleware.trace_context import TraceContextMiddleware
|
||||
from middleware.logging import RequestLoggingMiddleware
|
||||
from middleware.error_handler import register_exception_handlers
|
||||
from apps.app_a.router import router as app_a_router
|
||||
from apps.apple.router import router as app_b_router
|
||||
|
||||
# from apps.app_a.router import router as app_a_router
|
||||
# from apps.apple.router import router as app_b_router
|
||||
from apps.jd.router import router as jd_router
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -40,7 +42,9 @@ async def lifespan(app: FastAPI):
|
||||
if settings.otel_enabled:
|
||||
init_tracing()
|
||||
instrument_app(app)
|
||||
logger.info(f"OpenTelemetry initialized: endpoint={settings.otel_exporter_endpoint}")
|
||||
logger.info(
|
||||
f"OpenTelemetry initialized: endpoint={settings.otel_exporter_endpoint}"
|
||||
)
|
||||
|
||||
# Initialize Redis
|
||||
await init_redis()
|
||||
@@ -104,10 +108,9 @@ app.add_middleware(TraceContextMiddleware)
|
||||
# Register exception handlers
|
||||
register_exception_handlers(app)
|
||||
|
||||
# Include application routers
|
||||
app.include_router(app_a_router)
|
||||
app.include_router(app_b_router)
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
router.include_router(jd_router())
|
||||
app.include_router(router)
|
||||
|
||||
# Health check endpoint
|
||||
@app.get(
|
||||
@@ -119,7 +122,7 @@ app.include_router(app_b_router)
|
||||
200: {"description": "Service is healthy"},
|
||||
500: ERROR_RESPONSES[500],
|
||||
503: ERROR_RESPONSES[503],
|
||||
}
|
||||
},
|
||||
)
|
||||
async def health_check():
|
||||
"""
|
||||
@@ -159,7 +162,7 @@ async def health_check():
|
||||
description="Get API information",
|
||||
responses={
|
||||
200: {"description": "API information retrieved successfully"},
|
||||
}
|
||||
},
|
||||
)
|
||||
async def root():
|
||||
"""Root endpoint with API information."""
|
||||
|
||||
@@ -32,6 +32,12 @@ dependencies = [
|
||||
"brotli>=1.1.0",
|
||||
"psutil>=5.9.0",
|
||||
"pycryptodome>=3.21.0",
|
||||
"curl-cffi>=0.13.0",
|
||||
"fake-useragent>=2.2.0",
|
||||
"opentelemetry-instrumentation-requests>=0.59b0",
|
||||
"ddddocr>=1.5.6",
|
||||
"tenacity>=9.1.2",
|
||||
"pyexecjs>=1.5.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
398
uv.lock
generated
398
uv.lock
generated
@@ -1,6 +1,11 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.13"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'darwin'",
|
||||
"platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||
"(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiomysql"
|
||||
@@ -68,6 +73,26 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/9c/fc2331f538fbf7eedba64b2052e99ccf9ba9d6888e2f41441ee28847004b/asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734", size = 24050, upload-time = "2025-10-05T09:15:05.11Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.10.5"
|
||||
@@ -143,6 +168,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coloredlogs"
|
||||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "humanfriendly" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.11.0"
|
||||
@@ -260,6 +297,42 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl-cffi"
|
||||
version = "0.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "cffi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/3d/f39ca1f8fdf14408888e7c25e15eed63eac5f47926e206fb93300d28378c/curl_cffi-0.13.0.tar.gz", hash = "sha256:62ecd90a382bd5023750e3606e0aa7cb1a3a8ba41c14270b8e5e149ebf72c5ca", size = 151303, upload-time = "2025-08-06T13:05:42.988Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/d1/acabfd460f1de26cad882e5ef344d9adde1507034528cb6f5698a2e6a2f1/curl_cffi-0.13.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:434cadbe8df2f08b2fc2c16dff2779fb40b984af99c06aa700af898e185bb9db", size = 5686337, upload-time = "2025-08-06T13:05:28.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/1c/cdb4fb2d16a0e9de068e0e5bc02094e105ce58a687ff30b4c6f88e25a057/curl_cffi-0.13.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:59afa877a9ae09efa04646a7d068eeea48915a95d9add0a29854e7781679fcd7", size = 2994613, upload-time = "2025-08-06T13:05:31.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/3e/fdf617c1ec18c3038b77065d484d7517bb30f8fb8847224eb1f601a4e8bc/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d06ed389e45a7ca97b17c275dbedd3d6524560270e675c720e93a2018a766076", size = 7931353, upload-time = "2025-08-06T13:05:32.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/10/6f30c05d251cf03ddc2b9fd19880f3cab8c193255e733444a2df03b18944/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4e0de45ab3b7a835c72bd53640c2347415111b43421b5c7a1a0b18deae2e541", size = 7486378, upload-time = "2025-08-06T13:05:33.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/81/5bdb7dd0d669a817397b2e92193559bf66c3807f5848a48ad10cf02bf6c7/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8eb4083371bbb94e9470d782de235fb5268bf43520de020c9e5e6be8f395443f", size = 8328585, upload-time = "2025-08-06T13:05:35.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/c1/df5c6b4cfad41c08442e0f727e449f4fb5a05f8aa564d1acac29062e9e8e/curl_cffi-0.13.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:28911b526e8cd4aa0e5e38401bfe6887e8093907272f1f67ca22e6beb2933a51", size = 8739831, upload-time = "2025-08-06T13:05:37.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/91/6dd1910a212f2e8eafe57877bcf97748eb24849e1511a266687546066b8a/curl_cffi-0.13.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d433ffcb455ab01dd0d7bde47109083aa38b59863aa183d29c668ae4c96bf8e", size = 8711908, upload-time = "2025-08-06T13:05:38.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/e4/15a253f9b4bf8d008c31e176c162d2704a7e0c5e24d35942f759df107b68/curl_cffi-0.13.0-cp39-abi3-win_amd64.whl", hash = "sha256:66a6b75ce971de9af64f1b6812e275f60b88880577bac47ef1fa19694fa21cd3", size = 1614510, upload-time = "2025-08-06T13:05:40.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/0f/9c5275f17ad6ff5be70edb8e0120fdc184a658c9577ca426d4230f654beb/curl_cffi-0.13.0-cp39-abi3-win_arm64.whl", hash = "sha256:d438a3b45244e874794bc4081dc1e356d2bb926dcc7021e5a8fef2e2105ef1d8", size = 1365753, upload-time = "2025-08-06T13:05:41.879Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ddddocr"
|
||||
version = "1.5.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/cf/1243d5f0d03763a287375366f68eadb5c14418f5b3df00c09eb971e526a7/ddddocr-1.5.6.tar.gz", hash = "sha256:2839a940bfabe02e3284ef3f9d2a037292aa9f641f355b43a9b70bece9e1b73d", size = 75825027, upload-time = "2024-10-15T09:22:00.94Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/74/418c1c0be49463799f9eeb307a8aa4013ff5fca5e0387f0ef2762fcdb4e2/ddddocr-1.5.6-py3-none-any.whl", hash = "sha256:f13865b00e42de5c2507c1889ba73c2bacd218a49d15b928c2a5c82667062ac5", size = 75868010, upload-time = "2024-10-15T09:21:41.061Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
@@ -282,6 +355,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-useragent"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.120.0"
|
||||
@@ -297,6 +379,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/60/7a639ceaba54aec4e1d5676498c568abc654b95762d456095b6cb529b1ca/fastapi-0.120.0-py3-none-any.whl", hash = "sha256:84009182e530c47648da2f07eb380b44b69889a4acfd9e9035ee4605c5cfc469", size = 108243, upload-time = "2025-10-23T20:56:33.281Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "25.9.23"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/1f/3ee70b0a55137442038f2a33469cc5fddd7e0ad2abf83d7497c18a2b6923/flatbuffers-25.9.23.tar.gz", hash = "sha256:676f9fa62750bb50cf531b42a0a2a118ad8f7f797a511eda12881c016f093b12", size = 22067, upload-time = "2025-09-24T05:25:30.106Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/1b/00a78aa2e8fbd63f9af08c9c19e6deb3d5d66b4dda677a0f61654680ee89/flatbuffers-25.9.23-py2.py3-none-any.whl", hash = "sha256:255538574d6cb6d0a79a17ec8bc0d30985913b87513a01cce8bcdb6b4c44d0e2", size = 30869, upload-time = "2025-09-24T05:25:28.912Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.71.0"
|
||||
@@ -435,6 +526,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humanfriendly"
|
||||
version = "10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
@@ -472,8 +575,12 @@ source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiomysql" },
|
||||
{ name = "alembic" },
|
||||
{ name = "brotli" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "curl-cffi" },
|
||||
{ name = "ddddocr" },
|
||||
{ name = "email-validator" },
|
||||
{ name = "fake-useragent" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "greenlet" },
|
||||
{ name = "gunicorn" },
|
||||
@@ -483,16 +590,21 @@ dependencies = [
|
||||
{ name = "opentelemetry-instrumentation-fastapi" },
|
||||
{ name = "opentelemetry-instrumentation-httpx" },
|
||||
{ name = "opentelemetry-instrumentation-redis" },
|
||||
{ name = "opentelemetry-instrumentation-requests" },
|
||||
{ name = "opentelemetry-instrumentation-sqlalchemy" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "orjson" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyexecjs" },
|
||||
{ name = "pymysql" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "redis" },
|
||||
{ name = "sqlmodel" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
@@ -511,8 +623,12 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "aiomysql", specifier = ">=0.2.0" },
|
||||
{ name = "alembic", specifier = ">=1.14.0" },
|
||||
{ name = "brotli", specifier = ">=1.1.0" },
|
||||
{ name = "cryptography", specifier = ">=46.0.1" },
|
||||
{ name = "curl-cffi", specifier = ">=0.13.0" },
|
||||
{ name = "ddddocr", specifier = ">=1.5.6" },
|
||||
{ name = "email-validator", specifier = ">=2.3.0" },
|
||||
{ name = "fake-useragent", specifier = ">=2.2.0" },
|
||||
{ name = "fastapi", specifier = ">=0.120.0" },
|
||||
{ name = "greenlet", specifier = ">=3.2.4" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
@@ -524,11 +640,15 @@ requires-dist = [
|
||||
{ name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.59b0" },
|
||||
{ name = "opentelemetry-instrumentation-httpx", specifier = ">=0.59b0" },
|
||||
{ name = "opentelemetry-instrumentation-redis", specifier = ">=0.59b0" },
|
||||
{ name = "opentelemetry-instrumentation-requests", specifier = ">=0.59b0" },
|
||||
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = ">=0.59b0" },
|
||||
{ name = "opentelemetry-sdk", specifier = ">=1.38.0" },
|
||||
{ name = "orjson", specifier = ">=3.11.4" },
|
||||
{ name = "psutil", specifier = ">=5.9.0" },
|
||||
{ name = "pycryptodome", specifier = ">=3.21.0" },
|
||||
{ name = "pydantic", specifier = ">=2.12.3" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.11.0" },
|
||||
{ name = "pyexecjs", specifier = ">=1.5.1" },
|
||||
{ name = "pymysql", specifier = ">=1.1.1" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" },
|
||||
@@ -539,6 +659,7 @@ requires-dist = [
|
||||
{ name = "redis", specifier = ">=5.2.1" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.4" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||
{ name = "tenacity", specifier = ">=9.1.2" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" },
|
||||
]
|
||||
provides-extras = ["dev"]
|
||||
@@ -607,6 +728,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpmath"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.18.2"
|
||||
@@ -642,6 +772,97 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onnxruntime"
|
||||
version = "1.23.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coloredlogs" },
|
||||
{ name = "flatbuffers" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "sympy" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/41/fba0cabccecefe4a1b5fc8020c44febb334637f133acefc7ec492029dd2c/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:2ff531ad8496281b4297f32b83b01cdd719617e2351ffe0dba5684fb283afa1f", size = 17196337, upload-time = "2025-10-22T03:46:35.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/f9/2d49ca491c6a986acce9f1d1d5fc2099108958cc1710c28e89a032c9cfe9/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:162f4ca894ec3de1a6fd53589e511e06ecdc3ff646849b62a9da7489dee9ce95", size = 19157691, upload-time = "2025-10-22T03:46:43.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/a1/428ee29c6eaf09a6f6be56f836213f104618fb35ac6cc586ff0f477263eb/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45d127d6e1e9b99d1ebeae9bcd8f98617a812f53f46699eafeb976275744826b", size = 15226898, upload-time = "2025-10-22T03:46:30.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/2b/b57c8a2466a3126dbe0a792f56ad7290949b02f47b86216cd47d857e4b77/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bace4e0d46480fbeeb7bbe1ffe1f080e6663a42d1086ff95c1551f2d39e7872", size = 17382518, upload-time = "2025-10-22T03:47:05.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/93/aba75358133b3a941d736816dd392f687e7eab77215a6e429879080b76b6/onnxruntime-1.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:1f9cc0a55349c584f083c1c076e611a7c35d5b867d5d6e6d6c823bf821978088", size = 13470276, upload-time = "2025-10-22T03:47:31.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/3d/6830fa61c69ca8e905f237001dbfc01689a4e4ab06147020a4518318881f/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d2385e774f46ac38f02b3a91a91e30263d41b2f1f4f26ae34805b2a9ddef466", size = 15229610, upload-time = "2025-10-22T03:46:32.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ca/862b1e7a639460f0ca25fd5b6135fb42cf9deea86d398a92e44dfda2279d/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2b9233c4947907fd1818d0e581c049c41ccc39b2856cc942ff6d26317cee145", size = 17394184, upload-time = "2025-10-22T03:47:08.127Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python-headless"
|
||||
version = "4.11.0.86"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.38.0"
|
||||
@@ -763,6 +984,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/87/fef04827239ce84e2729b11611e8d5be7892288f620961ee9b9bafd035c5/opentelemetry_instrumentation_redis-0.59b0-py3-none-any.whl", hash = "sha256:8f7494dede5a6bfe5d8f20da67b371a502883398081856378380efef27da0bdf", size = 14946, upload-time = "2025-10-16T08:39:07.887Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-requests"
|
||||
version = "0.59b0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-instrumentation" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "opentelemetry-util-http" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/01/31282a46b09684dfc636bc066deb090bae6973e71e85e253a8c74e727b1f/opentelemetry_instrumentation_requests-0.59b0.tar.gz", hash = "sha256:9af2ffe3317f03074d7f865919139e89170b6763a0251b68c25e8e64e04b3400", size = 15186, upload-time = "2025-10-16T08:40:00.558Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/ea/c282ba418b2669e4f730cb3f68b02a0ca65f4baf801e971169a4cc449ffb/opentelemetry_instrumentation_requests-0.59b0-py3-none-any.whl", hash = "sha256:d43121532877e31a46c48649279cec2504ee1e0ceb3c87b80fe5ccd7eafc14c1", size = 12966, upload-time = "2025-10-16T08:39:09.919Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-instrumentation-sqlalchemy"
|
||||
version = "0.59b0"
|
||||
@@ -883,6 +1119,64 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
@@ -907,6 +1201,32 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.23"
|
||||
@@ -916,6 +1236,36 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.23.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.12.3"
|
||||
@@ -994,6 +1344,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyexecjs"
|
||||
version = "1.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/8e/aedef81641c8dca6fd0fb7294de5bed9c45f3397d67fddf755c1042c2642/PyExecJS-1.5.1.tar.gz", hash = "sha256:34cc1d070976918183ff7bdc0ad71f8157a891c92708c00c5fbbff7a769f505c", size = 13344, upload-time = "2018-01-18T04:33:55.126Z" }
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
@@ -1012,6 +1371,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl", hash = "sha256:e6b1d89711dd51f8f74b1631fe08f039e7d76cf67a42a323d3178f0f25762ed9", size = 45300, upload-time = "2025-08-24T12:55:53.394Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyreadline3"
|
||||
version = "3.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
@@ -1155,6 +1523,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956, upload-time = "2025-10-23T19:36:58.714Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
@@ -1210,6 +1587,27 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mpmath" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "9.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
|
||||
Reference in New Issue
Block a user