feat: 继续添加其他东西

This commit is contained in:
sunxiaolong
2024-08-11 22:06:50 +08:00
parent 652bbdf2f3
commit 95737121fd
32 changed files with 360 additions and 196 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
/.idea/
/.idea/
/data/

View File

@@ -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

View File

@@ -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()

View File

@@ -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
View 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()

View File

@@ -1 +1 @@
from .redis_db import cache
from .redis_db import redis_pool

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
from .settings import setting
from src.initialization.log import logger

View File

@@ -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",

View File

@@ -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(

View File

@@ -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))

View File

@@ -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):

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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")

View File

@@ -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())

View 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

View 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="数据")

View File

@@ -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="登录失败的状态")

View File

@@ -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,
# ),
# )

View File

@@ -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)

View File

View File

@@ -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())

View File

@@ -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())

View File

@@ -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())

View File

View File

@@ -1 +0,0 @@