mirror of
https://git.oceanpay.cc/danial/kami_itunes_third_api.git
synced 2025-12-18 22:20:08 +00:00
feat: 继续添加其他东西
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/.idea/
|
||||
/.idea/
|
||||
/data/
|
||||
@@ -1,7 +1,7 @@
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
logger:
|
||||
level: info
|
||||
level: INFO
|
||||
path: ./data/log
|
||||
port: 8080
|
||||
debug: true
|
||||
@@ -19,3 +19,6 @@ redis:
|
||||
port: 6379
|
||||
password: PASSWORD
|
||||
db: 0
|
||||
|
||||
masterNode:
|
||||
address: IP_ADDRESS
|
||||
5
main.py
5
main.py
@@ -1,4 +1,5 @@
|
||||
from src.config.settings import Settings
|
||||
from src.cmd.scripts import run
|
||||
from src.initialization import *
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(Settings())
|
||||
run()
|
||||
|
||||
@@ -2,3 +2,5 @@ requests~=2.31.0
|
||||
redis~=5.0.8
|
||||
pydantic~=2.8.2
|
||||
pydantic-settings~=2.4.0
|
||||
loguru~=0.7.2
|
||||
pyyaml~=6.0.1
|
||||
30
src/cmd/scripts.py
Normal file
30
src/cmd/scripts.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
from multiprocessing import Process
|
||||
|
||||
from src.integrations.master_node.api import MasterNodeService
|
||||
from src.service.service import ItunesService
|
||||
|
||||
|
||||
def run_task():
|
||||
"""
|
||||
执行任务
|
||||
|
||||
:return:
|
||||
"""
|
||||
itunes_service = ItunesService()
|
||||
master_node_service = MasterNodeService()
|
||||
|
||||
|
||||
def run():
|
||||
# 定义一个进程池
|
||||
proc = Process(target=run_task, daemon=True)
|
||||
# 启动进程池
|
||||
proc.start()
|
||||
|
||||
while True:
|
||||
print(f"主进程执行:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
time.sleep(1)
|
||||
if not proc.is_alive():
|
||||
proc = Process(target=run_task, daemon=True)
|
||||
proc.start()
|
||||
@@ -1 +1 @@
|
||||
from .redis_db import cache
|
||||
from .redis_db import redis_pool
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
from redis import asyncio as redis
|
||||
from typing import Optional
|
||||
|
||||
from redis import asyncio as aioredis
|
||||
from redis.asyncio import ConnectionPool
|
||||
|
||||
from src.initialization import setting
|
||||
|
||||
|
||||
class AsyncRedisManager:
|
||||
def __init__(self, url='redis://localhost', minsize=5, maxsize=10):
|
||||
def __init__(self, url="redis://localhost", minsize=5, maxsize=10):
|
||||
self.url = url
|
||||
self.minsize = minsize
|
||||
self.maxsize = maxsize
|
||||
self.pool = None
|
||||
self.pool: Optional[ConnectionPool] = None
|
||||
|
||||
async def connect(self):
|
||||
if not self.pool:
|
||||
self.pool = redis.ConnectionPool.from_url("redis://localhost")
|
||||
self.pool = aioredis.ConnectionPool.from_url(
|
||||
f"redis://{setting.redis.password}@{setting.redis.host}:{setting.redis.port}/0"
|
||||
)
|
||||
|
||||
async def disconnect(self):
|
||||
if self.pool:
|
||||
self.pool.close()
|
||||
await self.pool.wait_closed()
|
||||
await self.pool.aclose()
|
||||
|
||||
async def get_connection(self):
|
||||
if not self.pool:
|
||||
await self.connect()
|
||||
return await self.pool.get()
|
||||
|
||||
async def release_connection(self, connection):
|
||||
self.pool.release(connection)
|
||||
|
||||
async def execute(self, command, *args, **kwargs):
|
||||
async with self.pool.get() as r:
|
||||
result = await getattr(r, command)(*args, **kwargs)
|
||||
return result
|
||||
return self.pool.get_available_connection()
|
||||
|
||||
|
||||
cache = AsyncRedisManager()
|
||||
cache.connect()
|
||||
redis_pool = AsyncRedisManager()
|
||||
redis_pool.connect()
|
||||
|
||||
2
src/initialization/__init__.py
Normal file
2
src/initialization/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .settings import setting
|
||||
from src.initialization.log import logger
|
||||
@@ -1,6 +1,6 @@
|
||||
from loguru import logger
|
||||
|
||||
from src.config.settings import setting
|
||||
from src.initialization import setting
|
||||
|
||||
logger.add(
|
||||
f"{setting.server.logger.path}/{{time:YYYY-MM-DD}}.log",
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import Tuple, Type
|
||||
|
||||
from pydantic import BaseModel, Field, RedisDsn, AliasChoices, PostgresDsn
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import (
|
||||
BaseSettings,
|
||||
PydanticBaseSettingsSource,
|
||||
@@ -16,6 +15,7 @@ class DataBaseSettings(BaseSettings):
|
||||
user: str = Field(default="postgres", description="User")
|
||||
password: str = Field(default="", description="Password")
|
||||
database: str = Field(default="postgres", description="Database")
|
||||
model_config = SettingsConfigDict(extra="ignore")
|
||||
|
||||
|
||||
class ServerLoggerSettings(BaseSettings):
|
||||
@@ -38,11 +38,16 @@ class RedisSettings(BaseSettings):
|
||||
encoding: str = Field(default="utf-8", description="Encoding")
|
||||
|
||||
|
||||
class MasterNodeSettings(BaseSettings):
|
||||
address: str = Field(default="127.0.0.1", description="Address")
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
server: ServerSettings = Field(default_factory=ServerSettings)
|
||||
redis: RedisSettings = Field(default_factory=RedisSettings)
|
||||
database: DataBaseSettings = Field(default_factory=DataBaseSettings)
|
||||
model_config = SettingsConfigDict(yaml_file='config.yml', extra='ignore')
|
||||
masterNode: MasterNodeSettings = Field(default_factory=MasterNodeSettings)
|
||||
model_config = SettingsConfigDict(yaml_file="config.yml", extra="ignore")
|
||||
|
||||
@classmethod
|
||||
def settings_customise_sources(
|
||||
@@ -1,8 +1,11 @@
|
||||
import pickle
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
from src.integrations.itunes.models import ItunesLoginResponse, RedeemResponseModel
|
||||
from src.initialization import logger
|
||||
from src.integrations.itunes.models import ItunesLoginResponse, RedeemResponseModel, ItunesFailLoginPlistData, \
|
||||
ItunesSuccessLoginPlistData
|
||||
from src.integrations.itunes.utils import parse_xml
|
||||
from src.integrations.june.models import (
|
||||
ItunesLoginModel,
|
||||
@@ -42,7 +45,6 @@ class AppleClient:
|
||||
"Content-Type": "application/x-apple-plist; Charset=UTF-8",
|
||||
},
|
||||
cookies=cookies,
|
||||
verify=False,
|
||||
)
|
||||
return response.text
|
||||
|
||||
@@ -91,15 +93,30 @@ class AppleClient:
|
||||
params=params,
|
||||
data=sign_map.post,
|
||||
allow_redirects=False,
|
||||
verify=False,
|
||||
)
|
||||
if response.is_redirect:
|
||||
redirect_url = response.headers["Location"]
|
||||
groups = re.search("https://p(\d+)-buy.itunes.apple.com", redirect_url)
|
||||
server_id = groups.group(1)
|
||||
return self.login(sign_map, server_id)
|
||||
response_xml = parse_xml(response.text)
|
||||
return ItunesLoginResponse(serverId=server_id, response=response_xml)
|
||||
response_dict_data = parse_xml(response.text)
|
||||
if "failureType" in response_dict_data:
|
||||
status = 30
|
||||
# 账户被禁用
|
||||
if response_dict_data.get("metrics").get("dialogId") == "MZFinance.AccountDisabled":
|
||||
status = 14
|
||||
# 账户被锁定
|
||||
if response_dict_data.get("metrics").get("dialogId") == "MZFinance.DisabledAndFraudLocked":
|
||||
status = 14
|
||||
# 密码错误
|
||||
if response_dict_data.get("failureType") == "-5000":
|
||||
status = 13
|
||||
if status == 30:
|
||||
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)
|
||||
|
||||
def redeem(self, code: str, itunes: ItunesLoginModel):
|
||||
url = f"https://p{itunes.server_id}-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemCodeSrv"
|
||||
@@ -133,12 +150,13 @@ class AppleClient:
|
||||
has_4gb_limit=False,
|
||||
).to_xml(),
|
||||
headers=headers,
|
||||
verify=False,
|
||||
)
|
||||
response.encoding = "utf-8"
|
||||
ret = RedeemResponseModel.model_validate(response.json())
|
||||
print(ret)
|
||||
|
||||
# 导出cookies
|
||||
def export_cookies(self):
|
||||
return self.session.cookies.get_dict()
|
||||
def export_cookies(self) -> bytes:
|
||||
return pickle.dumps(self.session.cookies)
|
||||
|
||||
def import_cookies(self, cookies: bytes):
|
||||
self.session.cookies.update(pickle.loads(cookies))
|
||||
|
||||
@@ -76,7 +76,7 @@ class ItunesLoginPrivacyAcknowledgement(BaseModel):
|
||||
# )
|
||||
|
||||
|
||||
class ItunesLoginPlistData(BaseModel):
|
||||
class ItunesSuccessLoginPlistData(BaseModel):
|
||||
pings: list[str] = Field([], alias="pings")
|
||||
# account_info: ItunesLoginAccountInfo = Field(..., alias="accountInfo")
|
||||
alt_dsid: str = Field(..., alias="altDsid")
|
||||
@@ -94,14 +94,24 @@ class ItunesLoginPlistData(BaseModel):
|
||||
# )
|
||||
# account_flags: ItunesLoginAccountFlags = Field(..., alias="accountFlags")
|
||||
status: int = Field(..., alias="status")
|
||||
privacy_acknowledgement: ItunesLoginPrivacyAcknowledgement = Field(
|
||||
..., alias="privacyAcknowledgement"
|
||||
)
|
||||
# privacy_acknowledgement: ItunesLoginPrivacyAcknowledgement = Field(
|
||||
# ..., alias="privacyAcknowledgement"
|
||||
# )
|
||||
|
||||
|
||||
class ItunesFailLoginPlistData(BaseModel):
|
||||
pings: list = Field([], alias="pings")
|
||||
status: int = Field(..., alias="status")
|
||||
failureType: str = Field(..., alias="failureType")
|
||||
customerMessage: str = Field(..., alias="customerMessage")
|
||||
m_allowed: bool = Field(..., alias="m-allowed")
|
||||
|
||||
|
||||
class ItunesLoginResponse(BaseModel):
|
||||
server_id: str = Field(..., alias="serverId")
|
||||
response: ItunesLoginPlistData = Field(..., alias="response")
|
||||
response: ItunesSuccessLoginPlistData | ItunesFailLoginPlistData = Field(
|
||||
..., alias="response"
|
||||
)
|
||||
|
||||
|
||||
class RedeemResponseModel(BaseModel):
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from src.integrations.itunes.models import (
|
||||
ItunesLoginPlistData,
|
||||
ItunesLoginSubscriptionTerms,
|
||||
ItunesLoginAddress,
|
||||
ItunesLoginSubscriptionAccount,
|
||||
ItunesLoginAccountFlags,
|
||||
ItunesLoginSubscriptionFamily,
|
||||
ItunesLoginPrivacyAcknowledgement,
|
||||
)
|
||||
|
||||
|
||||
def parse_xml(xml_str: str) -> ItunesLoginPlistData | None:
|
||||
def parse_xml(xml_str: str) -> dict:
|
||||
root = ElementTree.fromstring(xml_str)
|
||||
dict_data = {}
|
||||
key = ""
|
||||
@@ -19,80 +9,7 @@ def parse_xml(xml_str: str) -> ItunesLoginPlistData | None:
|
||||
if child.tag == "key":
|
||||
key = child.text
|
||||
else:
|
||||
if key == "address":
|
||||
inner_dict = {}
|
||||
for sub_child in child:
|
||||
inner_key = sub_child.find("key").text
|
||||
inner_value = sub_child.find("string").text
|
||||
inner_dict[inner_key] = inner_value
|
||||
dict_data[key] = ItunesLoginAddress(**inner_dict)
|
||||
elif key == "terms":
|
||||
term_list = []
|
||||
for sub_child in child:
|
||||
term_dict = {}
|
||||
sub_key = ""
|
||||
for sub_sub_child in sub_child:
|
||||
if sub_sub_child.tag == "key":
|
||||
sub_key = sub_sub_child.text
|
||||
else:
|
||||
term_dict[sub_key] = (
|
||||
int(sub_sub_child.text)
|
||||
if sub_sub_child.tag == "integer"
|
||||
else sub_sub_child.text
|
||||
)
|
||||
term_list.append(ItunesLoginSubscriptionTerms(**term_dict))
|
||||
dict_data["terms"] = term_list
|
||||
elif key == "account":
|
||||
account_dict = {}
|
||||
sub_key = ""
|
||||
for sub_child in child:
|
||||
if sub_child.tag == "key":
|
||||
sub_key = sub_child.text
|
||||
else:
|
||||
account_dict[sub_key] = (
|
||||
bool(int(sub_child.text))
|
||||
if sub_child.tag == "integer"
|
||||
else sub_child.text
|
||||
)
|
||||
dict_data["account"] = ItunesLoginSubscriptionAccount(**account_dict)
|
||||
elif key == "family":
|
||||
family_dict = {}
|
||||
sub_key = ""
|
||||
for sub_child in child:
|
||||
if sub_child.tag == "key":
|
||||
sub_key = sub_child.text
|
||||
else:
|
||||
family_dict[sub_key] = (
|
||||
bool(int(sub_child.text))
|
||||
if sub_child.tag == "integer"
|
||||
else sub_child.text
|
||||
)
|
||||
dict_data["family"] = ItunesLoginSubscriptionFamily(**family_dict)
|
||||
elif key == "accountFlags":
|
||||
flags_dict = {}
|
||||
sub_key = ""
|
||||
for sub_child in child:
|
||||
if sub_child.tag == "key":
|
||||
sub_key = sub_child.text
|
||||
else:
|
||||
flags_dict[sub_key] = (
|
||||
bool(int(sub_child.text))
|
||||
if sub_child.tag == "integer"
|
||||
else sub_child.text
|
||||
)
|
||||
# dict_data["accountFlags"] = ItunesLoginAccountFlags(**flags_dict)
|
||||
elif key == "privacyAcknowledgement":
|
||||
privacy_dict = {}
|
||||
sub_key = ""
|
||||
for sub_child in child:
|
||||
if sub_child.tag == "key":
|
||||
sub_key = sub_child.text
|
||||
else:
|
||||
privacy_dict[sub_key] = int(sub_child.text)
|
||||
dict_data["privacyAcknowledgement"] = (
|
||||
ItunesLoginPrivacyAcknowledgement.model_validate(privacy_dict)
|
||||
)
|
||||
elif child.tag == "integer":
|
||||
if child.tag == "integer":
|
||||
dict_data[key] = int(child.text)
|
||||
elif child.tag == "string":
|
||||
dict_data[key] = child.text if child.text else ""
|
||||
@@ -102,4 +19,20 @@ def parse_xml(xml_str: str) -> ItunesLoginPlistData | None:
|
||||
dict_data[key] = False
|
||||
elif child.tag == "array":
|
||||
dict_data[key] = []
|
||||
return ItunesLoginPlistData(**dict_data)
|
||||
elif child.tag == "dict":
|
||||
dict_data[key] = parse_xml_tree([x for x in child])
|
||||
return dict_data
|
||||
|
||||
|
||||
def parse_xml_tree(tree_list: list[ElementTree]):
|
||||
dict_data = {}
|
||||
key = ""
|
||||
for child in tree_list:
|
||||
if child.tag == "key":
|
||||
key = child.text
|
||||
else:
|
||||
if child.tag == "string":
|
||||
dict_data[key] = child.text if child.text else ""
|
||||
if child.tag == "dict":
|
||||
dict_data[key] = parse_xml_tree([x for x in child])
|
||||
return dict_data
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import hashlib
|
||||
|
||||
from src.integrations.june.utils import MachineCode
|
||||
from src.integrations.june.models import LoginUserInfo
|
||||
from src.integrations.june.utils import MachineCode
|
||||
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -9,7 +9,7 @@ from urllib import parse
|
||||
import requests
|
||||
|
||||
from src.integrations.june.config import Config
|
||||
from src.integrations.june.utils import ShareCodeUtils, decode_and_decompress
|
||||
from src.integrations.june.crypto import encrypt_cbc_base64, encrypt
|
||||
from src.integrations.june.models import (
|
||||
AppleSixResponseModel,
|
||||
LoginUserInfo,
|
||||
@@ -17,7 +17,7 @@ from src.integrations.june.models import (
|
||||
LoginSignatureModel,
|
||||
AuthenticateModel,
|
||||
)
|
||||
from src.utils.crypto.crypto import encrypt_cbc_base64, encrypt
|
||||
from src.integrations.june.utils import ShareCodeUtils, decode_and_decompress
|
||||
|
||||
|
||||
class SixClient:
|
||||
|
||||
@@ -113,7 +113,9 @@ class ItunesRedeemModel(BaseModel):
|
||||
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")
|
||||
response_content_type: str = Field(
|
||||
alias="response-content-type", default="application/json"
|
||||
)
|
||||
|
||||
def to_xml(self):
|
||||
plist = ElementTree.Element("plist", version="1.0")
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from src.integrations.june.utils import ShareCodeUtils
|
||||
from src.integrations.june.utils import ShareCodeUtils, MachineCode
|
||||
|
||||
|
||||
class TestShareCodeUtils(TestCase):
|
||||
def test_code_to_id(self):
|
||||
print(ShareCodeUtils.code_to_id("2JPA"))
|
||||
|
||||
def test_get_cpu_info(self):
|
||||
print(MachineCode().get_cpu_info())
|
||||
print(MachineCode().get_hd_id())
|
||||
print(MachineCode().get_mo_address())
|
||||
|
||||
125
src/integrations/master_node/api.py
Normal file
125
src/integrations/master_node/api.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import time
|
||||
from threading import RLock
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
from src.initialization import setting
|
||||
from src.integrations.master_node.models import RechargeQueryModel, CommonResponseSchema
|
||||
|
||||
|
||||
class MasterNodeService:
|
||||
single_lock = RLock()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
with MasterNodeService.single_lock:
|
||||
if not hasattr(MasterNodeService, "_instance"):
|
||||
MasterNodeService._instance = object.__new__(cls)
|
||||
return MasterNodeService._instance
|
||||
|
||||
def __init__(self):
|
||||
self.__address = setting.masterNode.address
|
||||
self.client = requests.Session()
|
||||
self.account = ""
|
||||
self.password = ""
|
||||
|
||||
def query_order(self, retry_count: int = 5) -> RechargeQueryModel:
|
||||
if retry_count <= 0:
|
||||
return RechargeQueryModel()
|
||||
response = None
|
||||
try:
|
||||
response = self.client.post(
|
||||
urljoin(self.__address, "/api/cardInfo/appleCard/rechargeOrder/handler"),
|
||||
data={
|
||||
"machineId": "demo",
|
||||
},
|
||||
proxies={
|
||||
"http": "",
|
||||
"https": "",
|
||||
},
|
||||
headers={
|
||||
"tokenFrom": "iframe",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
response_json = response.json()
|
||||
if (
|
||||
not response_json
|
||||
or not response_json.get("data")
|
||||
or response_json.get("data", {}).get("cardPass") == ""
|
||||
or response_json.get("code") == 65
|
||||
):
|
||||
print(f"接口返回数据:{response_json}")
|
||||
else:
|
||||
logger.info(f"接口返回数据:{response_json}")
|
||||
except Exception as e:
|
||||
logger.error(f"请求数据异常,{e},")
|
||||
if isinstance(response, requests.Response):
|
||||
logger.error(f"返回结果:{response.text}")
|
||||
time.sleep(2)
|
||||
return self.query_order(retry_count - 1)
|
||||
result = CommonResponseSchema[RechargeQueryModel].model_validate(response_json)
|
||||
if not result.data or not result.data.account:
|
||||
return RechargeQueryModel()
|
||||
|
||||
self.account = result.data.account
|
||||
self.password = result.data.password
|
||||
return result.data
|
||||
|
||||
# 修改充值状态
|
||||
def update_order_status(
|
||||
self,
|
||||
order_no: str,
|
||||
status: int,
|
||||
remark: str,
|
||||
amount: float,
|
||||
account_amount: float = 0,
|
||||
retry_count: int = 10,
|
||||
):
|
||||
"""更新充值状态
|
||||
|
||||
Args:
|
||||
order_no (str): 账单ID
|
||||
status (int): 状态
|
||||
remark (str): 备注
|
||||
amount (float): 充值金额
|
||||
account_amount (float, optional): 账户余额. Defaults to 0.
|
||||
retry_count (int, optional):重试次数
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
if retry_count <= 0:
|
||||
return False
|
||||
logger.info(
|
||||
f"修改订单号:{order_no},状态:{status},金额:{amount},备注:{remark}"
|
||||
)
|
||||
try:
|
||||
response = self.client.post(
|
||||
urljoin(self.__address, "/api/cardInfo/appleCard/rechargeOrder/callback"),
|
||||
data={
|
||||
"machineId": "demo",
|
||||
"amount": amount,
|
||||
"orderNo": order_no,
|
||||
"accountAmount": account_amount,
|
||||
"status": status,
|
||||
"remark": remark,
|
||||
},
|
||||
proxies={
|
||||
"http": "",
|
||||
"https": "",
|
||||
},
|
||||
headers={
|
||||
"tokenFrom": "iframe",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
except Exception as e:
|
||||
time.sleep(2)
|
||||
logger.error(f"修改订单状态失败,{e}")
|
||||
return self.update_order_status(
|
||||
order_no, status, remark, amount, retry_count=retry_count - 1
|
||||
)
|
||||
logger.info(f"接口返回数据:{response.text}")
|
||||
return True
|
||||
20
src/integrations/master_node/models.py
Normal file
20
src/integrations/master_node/models.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
DataT = TypeVar("DataT")
|
||||
|
||||
|
||||
class RechargeQueryModel(BaseModel):
|
||||
account: str = Field(default="", description="账号")
|
||||
password: str = Field(default="", description="密码")
|
||||
|
||||
orderNo: str = Field(default="", description="订单号")
|
||||
cardNo: str = Field(default="", description="卡号")
|
||||
cardPass: str = Field(default="", description="密码")
|
||||
|
||||
|
||||
class CommonResponseSchema(BaseModel, Generic[DataT]):
|
||||
status: int = Field(default=0, description="0.失败 200.成功")
|
||||
message: str = Field(default="", description="信息")
|
||||
data: DataT | None = Field(default={}, description="数据")
|
||||
@@ -1 +1,14 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.integrations.june.models import ItunesLoginModel
|
||||
|
||||
|
||||
class LoginSuccessResponse(BaseModel):
|
||||
login_schema: ItunesLoginModel = Field(..., description="需要登录的信息")
|
||||
cookies: bytes = Field(..., description="登录后的cookie")
|
||||
|
||||
|
||||
class LoginFailureResponse(BaseModel):
|
||||
message: str = Field(..., description="登录失败的信息")
|
||||
failure_type: str = Field(..., description="登录失败的类型")
|
||||
status: int = Field(..., description="登录失败的状态")
|
||||
@@ -1,30 +1,53 @@
|
||||
import time
|
||||
|
||||
from src.initialization import logger
|
||||
from src.integrations.itunes.login import AppleClient
|
||||
from src.integrations.itunes.models 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
|
||||
|
||||
|
||||
class ItunesService:
|
||||
def login(self, account: AppleAccountModel):
|
||||
def __init__(self):
|
||||
self.june_client_service = SixClient()
|
||||
self.apple_client_service = AppleClient()
|
||||
|
||||
def login(self, account: AppleAccountModel) -> LoginSuccessResponse | LoginFailureResponse:
|
||||
"""
|
||||
登录itunes
|
||||
|
||||
:param account:
|
||||
:return:
|
||||
"""
|
||||
june_client_service = SixClient()
|
||||
apple_client = AppleClient()
|
||||
sign_sap_from_june = june_client_service.get_sign_sap_setup()
|
||||
sign_sap_setup_buffer = apple_client.query_sign_sap_setup(
|
||||
|
||||
sign_sap_from_june = self.june_client_service.get_sign_sap_setup()
|
||||
sign_sap_setup_buffer = self.apple_client_service.query_sign_sap_setup(
|
||||
sign_sap_from_june.Data
|
||||
)
|
||||
sign_sap_cert = june_client_service.get_sign_sap_setup_cert(
|
||||
sign_sap_cert = self.june_client_service.get_sign_sap_setup_cert(
|
||||
account, sign_sap_from_june, sign_sap_setup_buffer
|
||||
)
|
||||
login_schema = apple_client.login(sign_sap_cert.Data)
|
||||
|
||||
|
||||
login_schema = self.apple_client_service.login(sign_sap_cert.Data)
|
||||
if isinstance(login_schema.response, ItunesSuccessLoginPlistData):
|
||||
return LoginSuccessResponse(
|
||||
login_schema=ItunesLoginModel(
|
||||
server_id=login_schema.server_id,
|
||||
guid=sign_sap_cert.Data.guid,
|
||||
dsis=int(login_schema.response.ds_person_id),
|
||||
passwordToken=login_schema.response.password_token,
|
||||
),
|
||||
cookies=self.apple_client_service.export_cookies(),
|
||||
)
|
||||
if isinstance(login_schema.response, ItunesFailLoginPlistData):
|
||||
# 失败编码 -5000 AppleID或密码错误
|
||||
return LoginFailureResponse(
|
||||
status=login_schema.response.status,
|
||||
failure_type=login_schema.response.failureType,
|
||||
message=login_schema.response.customerMessage,
|
||||
)
|
||||
logger.warning("登录状态未知:", login_schema)
|
||||
|
||||
def redeem(self, code: str):
|
||||
"""
|
||||
@@ -33,13 +56,13 @@ class ItunesService:
|
||||
:param code:
|
||||
:return:
|
||||
"""
|
||||
apple_client = AppleClient()
|
||||
apple_client.redeem(
|
||||
"X3JC8LGLMGX9Z47T",
|
||||
ItunesLoginModel(
|
||||
server_id=login_schema.server_id,
|
||||
guid=sign_sap_cert.Data.guid,
|
||||
dsis=int(login_schema.response.ds_person_id),
|
||||
passwordToken=login_schema.response.password_token,
|
||||
),
|
||||
)
|
||||
pass
|
||||
# self.apple_client_service.redeem(
|
||||
# "X3JC8LGLMGX9Z47T",
|
||||
# ItunesLoginModel(
|
||||
# server_id=login_schema.server_id,
|
||||
# guid=sign_sap_cert.Data.guid,
|
||||
# dsis=int(login_schema.response.ds_person_id),
|
||||
# passwordToken=login_schema.response.password_token,
|
||||
# ),
|
||||
# )
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import time
|
||||
from unittest import TestCase
|
||||
|
||||
from src.integrations.june.models import AppleAccountModel
|
||||
from src.service.service import ItunesService
|
||||
|
||||
|
||||
class TestItunesService(TestCase):
|
||||
def test_login(self):
|
||||
print(ItunesService().login())
|
||||
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",
|
||||
}
|
||||
for k, v in d.items():
|
||||
account = AppleAccountModel(
|
||||
account=k,
|
||||
password=v,
|
||||
)
|
||||
ItunesService().login(account)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import random
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_encrypt_cbc_base64(self):
|
||||
# print(encrypt_cbc_base64(
|
||||
# 'db55eiJcE09FlwFZkxSziueyTJ6YQLoR5SV+VjRX242ECBnteKfcJBvR1aAp9H7CWDJCeR1RQLWyQ746Vmm98nr3C7lLYQ',
|
||||
# '1722657380000063', '90e7b0dc3ef2134c'
|
||||
# ))
|
||||
print(random.random())
|
||||
@@ -1,21 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from src.service.service import ItunesService
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_login(self):
|
||||
ItunesService().login()
|
||||
# SixClient()._do_post({
|
||||
# "gZip": "1",
|
||||
# "type": "GetSignsapsetup",
|
||||
# "userAgent": "MacAppStore/2.0 (Macintosh; OS X 12.10) AppleWebKit/600.1.3.41",
|
||||
# },
|
||||
# "ApiServiceSend",
|
||||
# )
|
||||
# x = json.dumps(json.dumps({
|
||||
# "gZip": "1",
|
||||
# "type": "GetSignsapsetup",
|
||||
# "userAgent": "MacAppStore/2.0 (Macintosh; OS X 12.10) AppleWebKit/600.1.3.41",
|
||||
# }, separators=(",", ":")))
|
||||
# print(hashlib.md5((f'{x}1722869145570849').encode()).hexdigest())
|
||||
@@ -1,10 +0,0 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from src.utils.utils import MachineCode
|
||||
|
||||
|
||||
class TestMachineCode(TestCase):
|
||||
def test_get_cpu_info(self):
|
||||
print(MachineCode().get_cpu_info())
|
||||
print(MachineCode().get_hd_id())
|
||||
print(MachineCode().get_mo_address())
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
Reference in New Issue
Block a user