mirror of
https://git.oceanpay.cc/danial/kami_itunes_third_api.git
synced 2025-12-18 22:17:53 +00:00
feat: 添加登录验证
This commit is contained in:
38
assets/json/充值成功.json
Normal file
38
assets/json/充值成功.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"woinst": -1,
|
||||
"creditDisplay": "¥16.00",
|
||||
"totalCredit": {
|
||||
"movieRentalBalance": 0,
|
||||
"songBalance": 0,
|
||||
"videoBalance": 0,
|
||||
"money": "¥16.00",
|
||||
"totalCredit": "¥16.00",
|
||||
"moneyRaw": 16.00,
|
||||
"gameBalance": 0,
|
||||
"tvRentalBalance": 0
|
||||
},
|
||||
"giftCertificateBonusId": 470000002770927,
|
||||
"dsPersonId": "130899799",
|
||||
"redeemCount": 1,
|
||||
"isOptedInForPersonalizedRecommendations": true,
|
||||
"isSubscription": false,
|
||||
"triggerDownload": false,
|
||||
"customSuccessData": {
|
||||
"isAmplifyRedemption": false,
|
||||
"isUpsell": false
|
||||
},
|
||||
"redeemedCredit": {
|
||||
"movieRentalBalance": 0,
|
||||
"songBalance": 0,
|
||||
"videoBalance": 0,
|
||||
"money": "¥6.00",
|
||||
"totalCredit": "¥6.00",
|
||||
"moneyRaw": 6.00,
|
||||
"gameBalance": 0,
|
||||
"tvRentalBalance": 0
|
||||
},
|
||||
"wosid": "KABUOHDqQs16eNR5vN62bw",
|
||||
"protocolVersion": "1.0",
|
||||
"email": "isaacf9zpierce@hotmail.com",
|
||||
"status": 0
|
||||
}
|
||||
2
main.py
2
main.py
@@ -11,4 +11,4 @@ if __name__ == "__main__":
|
||||
redis_conn = redis_pool.get_connection()
|
||||
redis_client = redis.Redis(connection_pool=redis_conn)
|
||||
redis_client.setex("test_count", 1000, pickle.dumps(json.dumps({"a": 1})))
|
||||
print(pickle.loads(redis_client.get("test_count")))
|
||||
print(pickle.loads(redis_client.get("test_count")))
|
||||
|
||||
@@ -6,4 +6,7 @@ loguru~=0.7.2
|
||||
pyyaml~=6.0.1
|
||||
brotli~=1.1.0
|
||||
wmi~=1.5.1
|
||||
pycryptodome~=3.20.0
|
||||
pycryptodome~=3.20.0
|
||||
sqlalchemy~=2.0.32
|
||||
zlib~=1.2.13
|
||||
pymysql~=1.1.1
|
||||
@@ -7,27 +7,27 @@ import redis
|
||||
from src.database import redis_pool
|
||||
from src.integrations.june.models import AppleAccountModel
|
||||
from src.integrations.master_node.api import MasterNodeService
|
||||
from src.integrations.master_node.models import RechargeQueryModel
|
||||
from src.models.model import AppleAccountSchema, LoginFailureResponse
|
||||
from src.service.service import ItunesService
|
||||
|
||||
|
||||
def run_task():
|
||||
"""
|
||||
执行任务
|
||||
|
||||
:return:
|
||||
"""
|
||||
def run_redeem_task(
|
||||
master_order: RechargeQueryModel, master_node_service: MasterNodeService
|
||||
):
|
||||
itunes_service = ItunesService()
|
||||
master_node_service = MasterNodeService()
|
||||
redis_conn = redis_pool.get_connection()
|
||||
redis_client = redis.Redis(connection_pool=redis_conn)
|
||||
master_order = master_node_service.query_order()
|
||||
|
||||
while True:
|
||||
response_schema_from_itunes = redis_client.get(f"apple_account_{master_order.account}")
|
||||
response_schema_from_itunes = redis_client.get(
|
||||
f"apple_account_{master_order.account}"
|
||||
)
|
||||
# 如果不存在
|
||||
if response_schema_from_itunes:
|
||||
apple_account_schema = AppleAccountSchema.model_validate_json(bytes(response_schema_from_itunes).decode())
|
||||
apple_account_schema = AppleAccountSchema.model_validate_json(
|
||||
bytes(response_schema_from_itunes).decode()
|
||||
)
|
||||
response_schema = apple_account_schema.login_schema
|
||||
if response_schema.status != 2:
|
||||
break
|
||||
@@ -44,7 +44,9 @@ def run_task():
|
||||
)
|
||||
redis_client.set(f"apple_account_{master_order.account}_count", 1)
|
||||
response_schema = itunes_service.login(
|
||||
AppleAccountModel(account=master_order.account, password=master_order.password)
|
||||
AppleAccountModel(
|
||||
account=master_order.account, password=master_order.password
|
||||
)
|
||||
)
|
||||
redis_client.incrby(f"apple_account_{master_order.account}_count", 1)
|
||||
if isinstance(response_schema, LoginFailureResponse):
|
||||
@@ -69,13 +71,22 @@ def run_task():
|
||||
)
|
||||
redis_client.decrby(f"apple_account_{master_order.account}_count", 1)
|
||||
else:
|
||||
if response_schema_from_itunes:
|
||||
redeem_result = itunes_service.redeem(master_order.cardPass, response_schema)
|
||||
redeem_result = itunes_service.redeem(
|
||||
master_order.cardPass,
|
||||
response_schema,
|
||||
response_schema_from_itunes is not None,
|
||||
)
|
||||
# 需要登录
|
||||
if redeem_result.status == 0:
|
||||
redis_client.delete(f"apple_account_{master_order.account}")
|
||||
redis_client.decrby(f"apple_account_{master_order.account}_count", 1)
|
||||
run_redeem_task(master_order, master_node_service)
|
||||
|
||||
# 兑换状态
|
||||
master_node_service.update_order_status(
|
||||
order_no=master_order.orderNo,
|
||||
status=response_schema.status, # 这边的登录状态与apple_account_schema的status是一样的
|
||||
remark=response_schema.message,
|
||||
status=redeem_result.status, # 这边的登录状态与apple_account_schema的status是一样的
|
||||
remark=redeem_result.errorMessage,
|
||||
amount=0,
|
||||
account_amount=0,
|
||||
)
|
||||
@@ -94,6 +105,17 @@ def run_task():
|
||||
redis_client.decrby(f"apple_account_{master_order.account}_count", 1)
|
||||
|
||||
|
||||
def run_task():
|
||||
"""
|
||||
执行任务
|
||||
|
||||
:return:
|
||||
"""
|
||||
master_node_service = MasterNodeService()
|
||||
master_order = master_node_service.query_order()
|
||||
run_redeem_task(master_order, master_node_service)
|
||||
|
||||
|
||||
def run():
|
||||
# 定义一个进程池
|
||||
proc = Process(target=run_task, daemon=True)
|
||||
|
||||
10
src/database/mysql_db.py
Normal file
10
src/database/mysql_db.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from src.initialization import setting
|
||||
|
||||
engine = create_engine(
|
||||
f"mysql+pymysql://{setting.database.user}:{setting.database.password}@{setting.database.host}:{setting.database.port}/{setting.database.database}",
|
||||
pool_size=100,
|
||||
echo_pool=True,
|
||||
echo=True,
|
||||
)
|
||||
@@ -2,12 +2,12 @@ from loguru import logger
|
||||
|
||||
from src.initialization import setting
|
||||
|
||||
logger.add(
|
||||
f"{setting.server.logger.path}/{{time:YYYY-MM-DD}}.log",
|
||||
rotation="1 days", # 每天生成新文件
|
||||
retention="20 days", # 保留最近10天的日志文件
|
||||
compression="zip", # 压缩旧日志文件
|
||||
encoding="utf-8", # 文件编码
|
||||
level=setting.server.logger.level,
|
||||
enqueue=True, # 异步写入日志
|
||||
)
|
||||
# logger.add(
|
||||
# f"{setting.server.logger.path}/{{time:YYYY-MM-DD}}.log",
|
||||
# rotation="1 days", # 每天生成新文件
|
||||
# retention="20 days", # 保留最近10天的日志文件
|
||||
# compression="zip", # 压缩旧日志文件
|
||||
# encoding="utf-8", # 文件编码
|
||||
# level=setting.server.logger.level,
|
||||
# enqueue=True, # 异步写入日志
|
||||
# )
|
||||
|
||||
@@ -47,7 +47,7 @@ class Settings(BaseSettings):
|
||||
redis: RedisSettings = Field(default_factory=RedisSettings)
|
||||
database: DataBaseSettings = Field(default_factory=DataBaseSettings)
|
||||
masterNode: MasterNodeSettings = Field(default_factory=MasterNodeSettings)
|
||||
model_config = SettingsConfigDict(yaml_file="config.yml", extra="ignore")
|
||||
model_config = SettingsConfigDict(yaml_file="./config/config.yml", extra="ignore")
|
||||
|
||||
@classmethod
|
||||
def settings_customise_sources(
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import json
|
||||
import pickle
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
from src.initialization import logger
|
||||
from src.integrations.itunes.models import (
|
||||
from src.integrations.itunes.models.login import (
|
||||
ItunesLoginResponse,
|
||||
RedeemResponseModel,
|
||||
ItunesFailLoginPlistData,
|
||||
ItunesSuccessLoginPlistData,
|
||||
)
|
||||
from src.integrations.itunes.utils import parse_xml
|
||||
from src.integrations.june.models import (
|
||||
ItunesLoginModel,
|
||||
ItunesRedeemModel,
|
||||
LoginSignatureModel,
|
||||
AuthenticateModel,
|
||||
from src.integrations.itunes.models.redeem import (
|
||||
RedeemFailResponseModel,
|
||||
RedeemSuccessResponse,
|
||||
)
|
||||
from src.integrations.itunes.utils import parse_xml
|
||||
from src.integrations.june.models.login import LoginSignatureModel, ItunesLoginModel
|
||||
from src.integrations.june.models.redeem import AuthenticateModel, ItunesRedeemModel
|
||||
|
||||
|
||||
class AppleClient:
|
||||
@@ -53,7 +53,7 @@ class AppleClient:
|
||||
return response.text
|
||||
|
||||
def login(
|
||||
self, sign_map: AuthenticateModel, server_id: str = ""
|
||||
self, sign_map: AuthenticateModel, server_id: str = "", retries: int = 5
|
||||
) -> ItunesLoginResponse:
|
||||
headers = {
|
||||
"X-Apple-ActionSignature": sign_map.signature,
|
||||
@@ -100,42 +100,46 @@ class AppleClient:
|
||||
)
|
||||
if response.is_redirect:
|
||||
redirect_url = response.headers["Location"]
|
||||
groups = re.search("https://p(\d+)-buy.itunes.apple.com", redirect_url)
|
||||
groups = re.search(r"https://p(\d+)-buy.itunes.apple.com", redirect_url)
|
||||
server_id = groups.group(1)
|
||||
return self.login(sign_map, server_id)
|
||||
return self.login(sign_map, server_id, retries - 1)
|
||||
response_dict_data = parse_xml(response.text)
|
||||
if "failureType" in response_dict_data:
|
||||
status = 30
|
||||
status = 31
|
||||
# 账户被禁用
|
||||
if (
|
||||
response_dict_data.get("metrics").get("dialogId")
|
||||
response_dict_data.get("metrics", {}).get("dialogId")
|
||||
== "MZFinance.AccountDisabled"
|
||||
):
|
||||
status = 14
|
||||
# 账户被锁定
|
||||
if (
|
||||
response_dict_data.get("metrics").get("dialogId")
|
||||
response_dict_data.get("metrics", {}).get("dialogId")
|
||||
== "MZFinance.DisabledAndFraudLocked"
|
||||
):
|
||||
status = 14
|
||||
# 密码错误
|
||||
if response_dict_data.get("failureType") == "-5000":
|
||||
status = 13
|
||||
if status == 30:
|
||||
if status == 31:
|
||||
logger.warning("登录状态未知:", response_dict_data)
|
||||
response_model = ItunesFailLoginPlistData(
|
||||
**{"status": status, **response_dict_data}
|
||||
)
|
||||
else:
|
||||
response_model = ItunesSuccessLoginPlistData(**response_dict_data)
|
||||
return ItunesLoginResponse(serverId=server_id, response=response_model)
|
||||
return ItunesLoginResponse(
|
||||
serverId=server_id, response=response_model, originLog=response.text
|
||||
)
|
||||
|
||||
def redeem(self, code: str, itunes: ItunesLoginModel):
|
||||
def redeem(
|
||||
self, code: str, itunes: ItunesLoginModel
|
||||
) -> RedeemSuccessResponse | RedeemFailResponseModel:
|
||||
url = f"https://p{itunes.server_id}-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemCodeSrv"
|
||||
headers = {
|
||||
"X-Apple-Store-Front": "143465-19,13",
|
||||
"X-Dsid": str(itunes.dsis),
|
||||
"X-Token": "9556C73C8ECF53AE81D321E0B58E5524",
|
||||
"X-Token": itunes.password_token,
|
||||
"X-Apple-Software-Guid": itunes.guid,
|
||||
"X-Apple-Partner": "origin.0",
|
||||
"X-Apple-Client-Application": "Software",
|
||||
@@ -146,8 +150,10 @@ class AppleClient:
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Content-Type": "application/x-apple-plist; Charset=UTF-8",
|
||||
"User-Agent": "MacAppStore/2.0 (Macintosh; OS X 12.10) AppleWebKit/600.1.3.41",
|
||||
"Referer": f"https://p{itunes.server_id}-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/com.apple.jingle.app.finance.DirectAction/redeemCode?cl=iTunes&pg=Music",
|
||||
"Referer": f"https://p{itunes.server_id}-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/com.apple"
|
||||
f".jingle.app.finance.DirectAction/redeemCode?cl=iTunes&pg=Music",
|
||||
}
|
||||
|
||||
response = self.session.post(
|
||||
url,
|
||||
data=ItunesRedeemModel(
|
||||
@@ -164,7 +170,43 @@ class AppleClient:
|
||||
headers=headers,
|
||||
)
|
||||
response.encoding = "utf-8"
|
||||
ret = RedeemResponseModel.model_validate(response.json())
|
||||
try:
|
||||
if "xml" in response.text:
|
||||
if "MZFinance.RedeemCodeSrvLoginRequired" in response.text:
|
||||
return RedeemFailResponseModel(
|
||||
status=0,
|
||||
errorMessageKey="",
|
||||
errorMessage="需要登录",
|
||||
originLog=response.text,
|
||||
userPresentableErrorMessage="需要登录",
|
||||
)
|
||||
else:
|
||||
if response.json().get("status") == 0:
|
||||
result = RedeemSuccessResponse.model_validate(response.json())
|
||||
result.originLog = response.text
|
||||
return result
|
||||
result = RedeemFailResponseModel.model_validate(response.json())
|
||||
result.originLog = response.text
|
||||
if (
|
||||
result.errorMessageKey
|
||||
== "MZCommerce.GiftCertificateAlreadyRedeemed"
|
||||
):
|
||||
result.status = 12
|
||||
if result.errorMessageKey == "MZFreeProductCode.NoSuch":
|
||||
result.status = 11
|
||||
if result.status == -1 or result.status == 0:
|
||||
result.status = 30
|
||||
logger.warning("兑换状态未知:", response.text)
|
||||
return result
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("json格式化失败", e)
|
||||
return RedeemFailResponseModel(
|
||||
status=30,
|
||||
errorMessageKey="",
|
||||
errorMessage="状态未知",
|
||||
originLog=response.text,
|
||||
userPresentableErrorMessage="状态未知",
|
||||
)
|
||||
|
||||
# 导出cookies
|
||||
def export_cookies(self) -> bytes:
|
||||
@@ -112,10 +112,4 @@ class ItunesLoginResponse(BaseModel):
|
||||
response: ItunesSuccessLoginPlistData | ItunesFailLoginPlistData = Field(
|
||||
..., alias="response"
|
||||
)
|
||||
|
||||
|
||||
class RedeemResponseModel(BaseModel):
|
||||
errorMessageKey: str = Field(..., alias="errorMessageKey")
|
||||
errorMessage: str = Field(..., alias="errorMessage")
|
||||
userPresentableErrorMessage: str = Field(..., alias="userPresentableErrorMessage")
|
||||
status: int = Field(..., alias="status")
|
||||
origin_log: str = Field(default="", alias="originLog")
|
||||
57
src/integrations/itunes/models/redeem.py
Normal file
57
src/integrations/itunes/models/redeem.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RedeemFailResponseModel(BaseModel):
|
||||
errorMessageKey: str = Field(default="", alias="errorMessageKey")
|
||||
errorMessage: str = Field(default="", alias="errorMessage")
|
||||
userPresentableErrorMessage: str = Field(
|
||||
default="", alias="userPresentableErrorMessage"
|
||||
)
|
||||
origin_log: str = Field(default="", alias="originLog")
|
||||
status: int = Field(..., alias="status", description="0.需要登录 1.正常")
|
||||
|
||||
|
||||
class TotalCreditModel(BaseModel):
|
||||
# movieRentalBalance: int
|
||||
# songBalance: int
|
||||
# videoBalance: int
|
||||
money: str = Field(default="")
|
||||
totalCredit: str = Field(default="")
|
||||
# moneyRaw: float
|
||||
# gameBalance: int
|
||||
# tvRentalBalance: int
|
||||
|
||||
|
||||
class CustomSuccessData(BaseModel):
|
||||
isAmplifyRedemption: bool
|
||||
isUpsell: bool
|
||||
|
||||
|
||||
class RedeemedCredit(BaseModel):
|
||||
movieRentalBalance: int
|
||||
songBalance: int
|
||||
videoBalance: int
|
||||
money: str
|
||||
totalCredit: str
|
||||
moneyRaw: float
|
||||
gameBalance: int
|
||||
tvRentalBalance: int
|
||||
|
||||
|
||||
class RedeemSuccessResponse(BaseModel):
|
||||
# woinst: int = Field(default=0)
|
||||
creditDisplay: str = Field(default="")
|
||||
totalCredit: TotalCreditModel = Field(default_factory=TotalCreditModel)
|
||||
# giftCertificateBonusId: int
|
||||
# dsPersonId: str
|
||||
# redeemCount: int
|
||||
# isOptedInForPersonalizedRecommendations: bool
|
||||
# isSubscription: bool
|
||||
# triggerDownload: bool
|
||||
# customSuccessData: CustomSuccessData
|
||||
# redeemedCredit: RedeemedCredit
|
||||
# wosid: str
|
||||
# protocolVersion: str
|
||||
email: str = Field(default="")
|
||||
status: int = Field(default=0)
|
||||
origin_log: str = Field(default="", alias="originLog")
|
||||
@@ -10,13 +10,13 @@ import requests
|
||||
|
||||
from src.integrations.june.config import Config
|
||||
from src.integrations.june.crypto import encrypt_cbc_base64, encrypt
|
||||
from src.integrations.june.models import (
|
||||
from src.integrations.june.models.login import (
|
||||
AppleSixResponseModel,
|
||||
LoginUserInfo,
|
||||
AppleAccountModel,
|
||||
LoginSignatureModel,
|
||||
AuthenticateModel,
|
||||
)
|
||||
from src.integrations.june.models.redeem import AuthenticateModel
|
||||
from src.integrations.june.utils import ShareCodeUtils, decode_and_decompress
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import hashlib
|
||||
|
||||
from src.integrations.june.models import LoginUserInfo
|
||||
from src.integrations.june.models.login import LoginUserInfo
|
||||
from src.integrations.june.utils import MachineCode
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, TypeVar, Generic
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -93,44 +92,3 @@ class AppleSixResponseModel(BaseModel, Generic[DataT]):
|
||||
extend: str = Field(default="")
|
||||
authenUserInfo: str = Field(default="")
|
||||
platformName: str = Field(default="")
|
||||
|
||||
|
||||
class AuthenticateModel(BaseModel):
|
||||
msg: str = Field(..., alias="msg")
|
||||
post: str = Field(..., alias="post")
|
||||
signature: str = Field(..., alias="signature")
|
||||
userAgent: str = Field(..., alias="userAgent")
|
||||
guid: str = Field(..., alias="guid")
|
||||
|
||||
|
||||
class ItunesRedeemModel(BaseModel):
|
||||
attempt_count: int = Field(default=0)
|
||||
camera_recognized_code: bool = Field(default=False)
|
||||
cl: str = Field(default="iTunes")
|
||||
code: str = Field(default="")
|
||||
ds_personId: int = Field(default=0)
|
||||
guid: str = Field(default="")
|
||||
has_4gb_limit: bool = Field(default=False)
|
||||
kbsync: str = Field(default="")
|
||||
pg: str = Field(default="")
|
||||
response_content_type: str = Field(
|
||||
alias="response-content-type", default="application/json"
|
||||
)
|
||||
|
||||
def to_xml(self):
|
||||
plist = ElementTree.Element("plist", version="1.0")
|
||||
dict_elem = ElementTree.SubElement(plist, "dict")
|
||||
for key, value in self.model_dump(by_alias=True).items():
|
||||
key_elem = ElementTree.SubElement(dict_elem, "key")
|
||||
key_elem.text = key
|
||||
if key == "kbsync":
|
||||
value_elem = ElementTree.SubElement(dict_elem, "data")
|
||||
value_elem.text = str(value)
|
||||
else:
|
||||
value_elem = ElementTree.SubElement(dict_elem, "string")
|
||||
value_elem.text = str(value)
|
||||
xml_str = ElementTree.tostring(plist, encoding="utf-8", method="xml").decode()
|
||||
# 添加XML声明
|
||||
xml_declaration = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
xml_str = xml_declaration + xml_str
|
||||
return xml_str
|
||||
44
src/integrations/june/models/redeem.py
Normal file
44
src/integrations/june/models/redeem.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AuthenticateModel(BaseModel):
|
||||
msg: str = Field(..., alias="msg")
|
||||
post: str = Field(..., alias="post")
|
||||
signature: str = Field(..., alias="signature")
|
||||
userAgent: str = Field(..., alias="userAgent")
|
||||
guid: str = Field(..., alias="guid")
|
||||
|
||||
|
||||
class ItunesRedeemModel(BaseModel):
|
||||
attempt_count: int = Field(default=0)
|
||||
camera_recognized_code: bool = Field(default=False)
|
||||
cl: str = Field(default="iTunes")
|
||||
code: str = Field(default="")
|
||||
ds_personId: int = Field(default=0)
|
||||
guid: str = Field(default="")
|
||||
has_4gb_limit: bool = Field(default=False)
|
||||
kbsync: str = Field(default="")
|
||||
pg: str = Field(default="")
|
||||
response_content_type: str = Field(
|
||||
alias="response-content-type", default="application/json"
|
||||
)
|
||||
|
||||
def to_xml(self):
|
||||
plist = ElementTree.Element("plist", version="1.0")
|
||||
dict_elem = ElementTree.SubElement(plist, "dict")
|
||||
for key, value in self.model_dump(by_alias=True).items():
|
||||
key_elem = ElementTree.SubElement(dict_elem, "key")
|
||||
key_elem.text = key
|
||||
if key == "kbsync":
|
||||
value_elem = ElementTree.SubElement(dict_elem, "data")
|
||||
value_elem.text = str(value)
|
||||
else:
|
||||
value_elem = ElementTree.SubElement(dict_elem, "string")
|
||||
value_elem.text = str(value)
|
||||
xml_str = ElementTree.tostring(plist, encoding="utf-8", method="xml").decode()
|
||||
# 添加XML声明
|
||||
xml_declaration = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
xml_str = xml_declaration + xml_str
|
||||
return xml_str
|
||||
@@ -18,3 +18,11 @@ class CommonResponseSchema(BaseModel, Generic[DataT]):
|
||||
status: int = Field(default=0, description="0.失败 200.成功")
|
||||
message: str = Field(default="", description="信息")
|
||||
data: DataT | None = Field(default={}, description="数据")
|
||||
|
||||
|
||||
class RedeemRequestModel(BaseModel):
|
||||
order_no: str = Field(default="", description="订单号")
|
||||
status: int = Field(default=0)
|
||||
remark: str = Field(default="", description="信息")
|
||||
amount: float = Field(default=0, description="金额")
|
||||
account_amount: float = Field(default=0, description="账户余额")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.integrations.june.models import ItunesLoginModel
|
||||
from src.integrations.june.models.login import ItunesLoginModel
|
||||
|
||||
|
||||
class LoginSuccessResponse(BaseModel):
|
||||
@@ -19,4 +19,11 @@ class AppleAccountSchema(BaseModel):
|
||||
password: str = Field(default="", description="密码")
|
||||
# 在线数量
|
||||
status: int = Field(default=0, description="0.失败 1.成功 2.正在登陆")
|
||||
login_schema: LoginSuccessResponse | LoginFailureResponse | None = Field(default=None, description="登录信息")
|
||||
login_schema: LoginSuccessResponse | LoginFailureResponse | None = Field(
|
||||
default=None, description="登录信息"
|
||||
)
|
||||
|
||||
|
||||
class AppleAccountRedeemResponse(BaseModel):
|
||||
status: int = Field(default=30, description="充值状态,20 充值成功")
|
||||
balance: float = Field(default=0, description="充值后金额")
|
||||
|
||||
50
src/schema/models.py
Normal file
50
src/schema/models.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, Integer, DateTime, String, Text, Float
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, Session
|
||||
|
||||
from src.database.mysql_db import engine
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class AppleCardLoginLogSchema(Base):
|
||||
__tablename__ = "apple_card_login_log"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment="自增ID")
|
||||
account_name = Column(String, comment="登录账户")
|
||||
account_password = Column(String, comment="账户密码")
|
||||
origin_log = Column(Text, comment="原始日志内容")
|
||||
status = Column(Integer, comment="登录状态")
|
||||
created_at = Column(
|
||||
DateTime, default=datetime.now, nullable=False, comment="登录时间"
|
||||
)
|
||||
|
||||
|
||||
class AppleCardRedeemLogSchema(Base):
|
||||
__tablename__ = "apple_card_redeem_log"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment="自增ID")
|
||||
apple_card_login_log_id = Column(Integer, comment="登录日志内容")
|
||||
order_no = Column(String, comment="订单号")
|
||||
redeem_status = Column(Integer, comment="充值状态")
|
||||
# 充值账户
|
||||
account_name = Column(String, comment="充值账户")
|
||||
amount = Column(Float, comment="充值金额")
|
||||
account_balance = Column(Float, comment="充值后账户余额")
|
||||
|
||||
card_pass = Column(String, comment="充值卡密")
|
||||
remark = Column(String, comment="备注")
|
||||
# 原始日志内容
|
||||
origin_log = Column(Text, comment="原始日志内容")
|
||||
# 充值时间
|
||||
created_at = Column(
|
||||
DateTime, default=datetime.now, nullable=False, comment="充值时间"
|
||||
)
|
||||
|
||||
|
||||
# 创建表结构
|
||||
# Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
def get_session() -> sessionmaker[Session]:
|
||||
# 创建会话工厂
|
||||
return sessionmaker(bind=engine)
|
||||
@@ -1,12 +1,22 @@
|
||||
from src.initialization import logger
|
||||
from src.integrations.itunes.login import AppleClient
|
||||
from src.integrations.itunes.models import (
|
||||
from src.integrations.itunes.api import AppleClient
|
||||
from src.integrations.itunes.models.login import (
|
||||
ItunesSuccessLoginPlistData,
|
||||
ItunesFailLoginPlistData,
|
||||
)
|
||||
from src.integrations.june.june import SixClient
|
||||
from src.integrations.june.models import AppleAccountModel, ItunesLoginModel
|
||||
from src.models.model import LoginSuccessResponse, LoginFailureResponse
|
||||
from src.integrations.itunes.models.redeem import (
|
||||
RedeemFailResponseModel,
|
||||
RedeemSuccessResponse,
|
||||
)
|
||||
|
||||
from src.integrations.june.api import SixClient
|
||||
from src.integrations.june.models.login import AppleAccountModel, ItunesLoginModel
|
||||
from src.models.model import (
|
||||
LoginSuccessResponse,
|
||||
LoginFailureResponse,
|
||||
AppleAccountRedeemResponse,
|
||||
)
|
||||
from src.schema.models import get_session, AppleCardLoginLogSchema
|
||||
|
||||
|
||||
class ItunesService:
|
||||
@@ -15,11 +25,14 @@ class ItunesService:
|
||||
self.apple_client_service = AppleClient()
|
||||
|
||||
def login(
|
||||
self, account: AppleAccountModel
|
||||
self,
|
||||
account: AppleAccountModel,
|
||||
retries: int = 3,
|
||||
) -> LoginSuccessResponse | LoginFailureResponse:
|
||||
"""
|
||||
登录itunes
|
||||
|
||||
:param retries:
|
||||
:param account:
|
||||
:return:
|
||||
"""
|
||||
@@ -32,7 +45,23 @@ class ItunesService:
|
||||
account, sign_sap_from_june, sign_sap_setup_buffer
|
||||
)
|
||||
login_schema = self.apple_client_service.login(sign_sap_cert.Data)
|
||||
# session = get_session()()
|
||||
# session.add(
|
||||
# AppleCardLoginLogSchema(
|
||||
# account_name=account.account,
|
||||
# account_password=account.password,
|
||||
# origin_log=login_schema.origin_log,
|
||||
# )
|
||||
# )
|
||||
if isinstance(login_schema.response, ItunesSuccessLoginPlistData):
|
||||
# session.add(
|
||||
# AppleCardLoginLogSchema(
|
||||
# account_name=account.account,
|
||||
# account_password=account.password,
|
||||
# status=1,
|
||||
# origin_log=login_schema.origin_log,
|
||||
# )
|
||||
# )
|
||||
return LoginSuccessResponse(
|
||||
login_schema=ItunesLoginModel(
|
||||
server_id=login_schema.server_id,
|
||||
@@ -43,15 +72,35 @@ class ItunesService:
|
||||
cookies=self.apple_client_service.export_cookies(),
|
||||
)
|
||||
if isinstance(login_schema.response, ItunesFailLoginPlistData):
|
||||
# session.add(
|
||||
# AppleCardLoginLogSchema(
|
||||
# account_name=account.account,
|
||||
# account_password=account.password,
|
||||
# status=0,
|
||||
# origin_log=login_schema.origin_log,
|
||||
# )
|
||||
# )
|
||||
# 失败编码 -5000 AppleID或密码错误
|
||||
return LoginFailureResponse(
|
||||
status=login_schema.response.status,
|
||||
failure_type=login_schema.response.failureType,
|
||||
message=login_schema.response.customerMessage,
|
||||
)
|
||||
# session.add(
|
||||
# AppleCardLoginLogSchema(
|
||||
# account_name=account.account,
|
||||
# account_password=account.password,
|
||||
# status=-1,
|
||||
# origin_log=login_schema.origin_log,
|
||||
# )
|
||||
# )
|
||||
# session.commit()
|
||||
# session.close()
|
||||
logger.warning("登录状态未知:", login_schema)
|
||||
|
||||
def redeem(self, code: str, schema: LoginSuccessResponse, set_cookie: bool = False):
|
||||
def redeem(
|
||||
self, code: str, schema: LoginSuccessResponse, set_cookie: bool = False
|
||||
) -> AppleAccountRedeemResponse:
|
||||
"""
|
||||
兑换代码
|
||||
|
||||
@@ -62,9 +111,9 @@ class ItunesService:
|
||||
"""
|
||||
if set_cookie:
|
||||
self.apple_client_service.import_cookies(schema.cookies)
|
||||
|
||||
self.apple_client_service.redeem(
|
||||
"X3JC8LGLMGX9Z47T",
|
||||
# session = get_session()()
|
||||
result = self.apple_client_service.redeem(
|
||||
code,
|
||||
ItunesLoginModel(
|
||||
server_id=schema.login_schema.server_id,
|
||||
guid=schema.login_schema.guid,
|
||||
@@ -72,3 +121,14 @@ class ItunesService:
|
||||
passwordToken=schema.login_schema.password_token,
|
||||
),
|
||||
)
|
||||
if isinstance(result, RedeemSuccessResponse):
|
||||
result = AppleAccountRedeemResponse(
|
||||
status=20, balance=float(result.creditDisplay.replace("¥", ""))
|
||||
)
|
||||
else:
|
||||
result = AppleAccountRedeemResponse(
|
||||
status=result.status,
|
||||
)
|
||||
# session.commit()
|
||||
# session.close()
|
||||
return result
|
||||
|
||||
@@ -1,25 +1,80 @@
|
||||
import time
|
||||
from unittest import TestCase
|
||||
|
||||
from src.integrations.june.models import AppleAccountModel
|
||||
from loguru import logger
|
||||
|
||||
from src.integrations.june.models.login import AppleAccountModel
|
||||
from src.models.model import LoginSuccessResponse
|
||||
from src.service.service import ItunesService
|
||||
|
||||
|
||||
class TestItunesService(TestCase):
|
||||
def test_login(self):
|
||||
start_time = time.time()
|
||||
d = {
|
||||
"jaydengm6ruiz@hotmail.com": "Eq99115",
|
||||
"john6veperez@hotmail.com": "Eq991155",
|
||||
"jacb1eortega@hotmail.com": "Eq991155",
|
||||
"grayson5s4d@hotmail.com": "Eq991155",
|
||||
"dylan336garcia@hotmail.com": "Eq991155",
|
||||
"henryifrdunn@hotmail.com": "Eq991155",
|
||||
"davidburkew7c@hotmail.com": "Eq991155",
|
||||
"isaacf9zpierce@hotmail.com": "Eq991155",
|
||||
# "carter5bthomas@hotmail.com": "Eq991155",
|
||||
# "jaydencolias@hotmail.com": "Eq991155",
|
||||
# "wyattc5stone@hotmail.com": "Eq991155",
|
||||
# "mason9vrzc@hotmail.com": "Eq991155",
|
||||
# "carter9o9zpierce@hotmail.com": "Eq991155",
|
||||
# "jack5uum@hotmail.com": "Eq991155",
|
||||
# "johnq18igu@hotmail.com": "Eq991155",
|
||||
# "jaydengm6ruiz@hotmail.com": "Eq991155",
|
||||
# "john6veperez@hotmail.com": "Eq991155",
|
||||
# "jacb1eortega@hotmail.com": "Eq991155",
|
||||
# "grayson5s4d@hotmail.com": "Eq991155",
|
||||
# "dylan336garcia@hotmail.com": "Eq991155",
|
||||
# "henryifrdunn@hotmail.com": "Eq991155",
|
||||
# "davidburkew7c@hotmail.com": "Eq991155",
|
||||
# "asherhillja1n@hotmail.com": "Eq991155",
|
||||
# "davidgreene6l8r@hotmail.com": "Eq991155",
|
||||
# "ezrajen9l@hotmail.com": "Eq991155",
|
||||
# "leopatel7o0@hotmail.com": "Eq991155",
|
||||
# "oliverlaneco@hotmail.com": "Eq991155",
|
||||
# "ezraarmrios@hotmail.com": "Eq991155",
|
||||
# "owenpfsnyder@hotmail.com": "Eq991155",
|
||||
# "thomasnelson61j0@hotmail.com": "Eq991155",
|
||||
# "josephdiazsn5c@hotmail.com": "Eq991155",
|
||||
# "gma6qmartin@hotmail.com": "Eq991155",
|
||||
# "elijah9fnstone@hotmail.com": "Eq991155",
|
||||
# "wyattcooper30@hotmail.com": "Eq991155",
|
||||
# "liamkcrygraham@hotmail.com": "Eq991155",
|
||||
# "levisotol9@hotmail.com": "Eq991155",
|
||||
# "levisutnbrown@hotmail.com": "Eq991155",
|
||||
# "isaacvaeoju@hotmail.com": "Eq991155",
|
||||
# "liampcipena@hotmail.com": "Eq991155",
|
||||
# "jalewisnj@hotmail.com": "Eq991155",
|
||||
# "anknlee@hotmail.com": "Eq991155",
|
||||
# "hudsonkpk4c@hotmail.com": "Eq991155",
|
||||
# "masonwv86s@hotmail.com": "Eq991155",
|
||||
# "isaac7s5vmyers@hotmail.com": "Eq991155",
|
||||
# "thomasrogersufu@hotmail.com": "Eq991155",
|
||||
# "williamz5nvke@hotmail.com": "Eq991155",
|
||||
# "aidenwagnerhi@hotmail.com": "Eq991155",
|
||||
# "gragreencscq@hotmail.com": "Eq991155",
|
||||
# "aidenh4ozjones@hotmail.com": "Eq991155",
|
||||
# "jacobrobyo@hotmail.com": "Eq991155",
|
||||
# "jaydenqdxgeorge@hotmail.com": "Eq991155",
|
||||
# "bbrown9f@hotmail.com": "Eq991155",
|
||||
# "gabtuqblack@hotmail.com": "Eq991155",
|
||||
# "hudsonmendezdxyc@hotmail.com": "Eq991155",
|
||||
# "josiahperfm@hotmail.com": "Eq991155",
|
||||
# "josiahsalo6km@hotmail.com": "Eq991155",
|
||||
# "lukekri1hayes@hotmail.com": "Eq991155",
|
||||
# "lucas52bstucker@hotmail.com": "Eq991155",
|
||||
}
|
||||
for k, v in d.items():
|
||||
service = ItunesService()
|
||||
for index, (k, v) in enumerate(d.items(), start=1):
|
||||
logger.info((index, k, v))
|
||||
account = AppleAccountModel(
|
||||
account=k,
|
||||
password=v,
|
||||
)
|
||||
ItunesService().login(account)
|
||||
result_schema = service.login(account)
|
||||
if isinstance(result_schema, LoginSuccessResponse):
|
||||
result = service.redeem("X7ZWF6V4M626Z4MT", schema=result_schema)
|
||||
logger.info(result)
|
||||
else:
|
||||
logger.info(result_schema)
|
||||
print(result_schema)
|
||||
time.sleep(1)
|
||||
|
||||
Reference in New Issue
Block a user