-
Notifications
You must be signed in to change notification settings - Fork 159
Expand file tree
/
Copy pathpinzan.py
More file actions
334 lines (283 loc) · 11.3 KB
/
pinzan.py
File metadata and controls
334 lines (283 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#!/usr/bin/python3
# -- coding: utf-8 --
# -------------------------------
# @Author : github@wd210010 https://github.com/wd210010/only_for_happly
# @Time : 2025/12/24 9:23
# -------------------------------
# cron "0 0 8 * * *" script-path=xxx.py,tag=匹配cron用
# const $ = new Env('品赞签到')
# 变量名:Pzandaili ACCOUNT1#PASSWORD1@ACCOUNT2#PASSWORD2 或 & 分隔
# 品赞注册链接:https://www.ipzan.com?pid=b8quopmio
import warnings
import os
import random
import base64
import requests
import time
from dataclasses import dataclass
from typing import List, Optional, Dict, Any
from contextlib import contextmanager
# -------------------------- 全局配置 --------------------------
# 基础URL配置
CONFIG_URLS = {
"home": "https://www.ipzan.com?pid=b1sf3o4ao",
"login": "https://service.ipzan.com/users-login",
"receive": "https://service.ipzan.com/home/userWallet-receive"
}
# 环境变量配置
ENV_CONFIG = {
"var_name": "Pzandaili",
"separators": ["@", "&"], # 多账户分隔符
"fixed_key": "QWERIPZAN1290QWER" # 加密固定密钥
}
# 请求配置
REQUEST_CONFIG = {
"timeout": 15,
"sleep_range": (2, 5), # 多账户延迟范围
"retry_times": 1, # 接口重试次数
"user_agents": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
]
}
# 关闭SSL警告
warnings.filterwarnings("ignore")
# -------------------------- 数据结构定义 --------------------------
@dataclass
class AccountInfo:
"""账户信息数据类"""
index: int
account: str
password: str
@dataclass
class ProcessResult:
"""账户处理结果数据类"""
account_index: int
account_masked: str
status: str # success/failed/duplicate/error
message: str
raw_data: Optional[Any] = None
# -------------------------- 工具函数 --------------------------
def get_random_user_agent() -> str:
"""获取随机User-Agent"""
return random.choice(REQUEST_CONFIG["user_agents"])
def get_base_headers() -> Dict[str, str]:
"""获取基础请求头"""
return {
"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",
"Connection": "keep-alive",
"Content-Type": "application/json;charset=UTF-8",
"Origin": "https://ipzan.com",
"Referer": "https://ipzan.com/",
"User-Agent": get_random_user_agent()
}
def mask_account(account: str) -> str:
"""账号脱敏显示"""
if len(account) > 7:
return f"{account[:3]}****{account[-4:]}"
return account[:2] + "****" + account[-1:] if len(account) > 3 else "****"
@contextmanager
def request_session():
"""请求会话上下文管理器,自动关闭会话"""
session = requests.Session()
try:
session.headers.update(get_base_headers())
yield session
finally:
session.close()
def retry_on_failure(max_retries: int = 1):
"""重试装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
if attempt < max_retries:
sleep_time = random.uniform(1, 3)
print(f"⚠️ 尝试{attempt+1}失败,{sleep_time:.1f}秒后重试:{str(e)[:50]}")
time.sleep(sleep_time)
else:
raise e
raise last_exception
return wrapper
return decorator
# -------------------------- 核心加密逻辑 --------------------------
def encrypt_account(phone: str, password: str) -> str:
"""IPZAN专属加密逻辑(官网JS还原)"""
# 基础加密
plain_text = f"{phone}{ENV_CONFIG['fixed_key']}{password}"
encoded_str = base64.b64encode(plain_text.encode("utf-8")).decode("utf-8")
# 生成400位混淆字符串
random_hex = []
for _ in range(80):
hex_str = hex(int(random.random() * 10**16))[2:]
random_hex.append(hex_str)
random_hex = "".join(random_hex).ljust(400, "0")[:400]
# 分段拼接
parts = [
random_hex[:100], encoded_str[:8],
random_hex[100:200], encoded_str[8:20],
random_hex[200:300], encoded_str[20:],
random_hex[300:400]
]
return "".join(parts)
# -------------------------- 账户加载 --------------------------
def load_accounts_from_env() -> List[AccountInfo]:
"""加载环境变量中的账户信息,返回结构化的账户列表"""
env_value = os.getenv(ENV_CONFIG["var_name"])
if not env_value:
raise ValueError(
f"❌ 环境变量 {ENV_CONFIG['var_name']} 未设置!\n"
f"📝 配置格式参考:\n"
f" - 单账户:账号#密码\n"
f" - 多账户:账号1#密码1@账号2#密码2(或&分隔)"
)
# 解析多账户分隔符
accounts_str_list = [env_value]
for sep in ENV_CONFIG["separators"]:
if sep in env_value:
accounts_str_list = [s.strip() for s in env_value.split(sep) if s.strip()]
break
# 解析每个账户信息
accounts = []
for idx, account_str in enumerate(accounts_str_list, 1):
parts = account_str.split("#", 2)
# 格式校验(忽略推送链接部分)
if len(parts) < 2:
print(f"⚠️ 跳过格式错误账户{idx}:{account_str}(需为 账号#密码 格式)")
continue
acc, pwd = parts[0].strip(), parts[1].strip()
if acc and pwd:
accounts.append(AccountInfo(
index=idx,
account=acc,
password=pwd
))
if not accounts:
raise ValueError("❌ 未加载到有效账户!请检查环境变量配置。")
return accounts
# -------------------------- 核心业务逻辑 --------------------------
@retry_on_failure(max_retries=REQUEST_CONFIG["retry_times"])
def login_account(session: requests.Session, account: str, password: str) -> str:
"""账户登录,返回Token"""
login_headers = get_base_headers()
login_headers["Authorization"] = "Bearer null"
encrypted_account = encrypt_account(account, password)
login_data = {"account": encrypted_account, "source": "ipzan-home-one"}
response = session.post(
url=CONFIG_URLS["login"],
headers=login_headers,
json=login_data,
timeout=REQUEST_CONFIG["timeout"]
)
response.raise_for_status()
login_result = response.json()
if login_result.get("code") != 0:
raise ValueError(f"登录失败:{login_result.get('message', '未知错误')}")
token = login_result.get("data", {}).get("token")
if not token:
raise ValueError("登录成功但未返回Token")
return token
@retry_on_failure(max_retries=REQUEST_CONFIG["retry_times"])
def receive_ip(session: requests.Session, token: str) -> Dict[str, Any]:
"""领取IP,返回领取结果"""
receive_headers = get_base_headers()
receive_headers["Authorization"] = f"Bearer {token}"
response = session.get(
url=CONFIG_URLS["receive"],
headers=receive_headers,
timeout=REQUEST_CONFIG["timeout"]
)
response.raise_for_status()
return response.json()
def process_single_account(account_info: AccountInfo) -> ProcessResult:
"""处理单个账户的完整流程"""
account_masked = mask_account(account_info.account)
print(f"\n[{account_info.index}] 📱 开始处理账号:{account_masked}")
try:
with request_session() as session:
# 1. 登录
token = login_account(session, account_info.account, account_info.password)
print(" └─ ✅ 登录成功")
# 2. 领取IP
receive_result = receive_ip(session, token)
code = receive_result.get("code")
msg = receive_result.get("message", "")
data = receive_result.get("data", "")
# 3. 处理领取结果
if code == 0:
status = "success"
message = f"领取成功:{data}"
print(f" └─ 🎉 {message}")
elif code == -1 and "领取过" in str(msg):
status = "duplicate"
message = f"本周已领:{msg}"
print(f" └─ ⚠️ {message}")
else:
status = "failed"
message = f"领取失败:{msg}"
print(f" └─ ❌ {message}")
return ProcessResult(
account_index=account_info.index,
account_masked=account_masked,
status=status,
message=message,
raw_data=receive_result
)
except Exception as e:
error_msg = f"运行异常:{str(e)[:50]}"
print(f" └─ ❌ {error_msg}")
return ProcessResult(
account_index=account_info.index,
account_masked=account_masked,
status="error",
message=error_msg
)
# -------------------------- 主程序 --------------------------
def main():
"""主程序入口"""
print("="*60)
print("🎯 IPZAN 多账户自动领取工具(每周领IP)- 无推送版")
print(f"🔗 注册链接:{CONFIG_URLS['home']}")
print("="*60)
all_summaries = []
try:
# 1. 加载账户
accounts = load_accounts_from_env()
print(f"\n📊 共加载 {len(accounts)} 个有效账户,开始执行任务...")
# 2. 处理每个账户
for idx, account in enumerate(accounts):
result = process_single_account(account)
# 构建汇总信息
status_icon = {
"success": "✅",
"failed": "❌",
"duplicate": "⚠️",
"error": "❌"
}.get(result.status, "❓")
summary = f"账号{result.account_index}:{result.account_masked}\n{status_icon} {result.message}"
all_summaries.append(summary)
# 多账户延迟
if idx < len(accounts) - 1:
sleep_time = random.uniform(*REQUEST_CONFIG["sleep_range"])
print(f"\n⏳ 多账户防并发,延迟 {sleep_time:.1f} 秒...")
time.sleep(sleep_time)
except Exception as e:
error_summary = f"❌ 程序全局错误:{str(e)[:100]}"
print(f"\n{error_summary}")
all_summaries.append(error_summary)
finally:
# 输出最终结果
print("\n" + "="*60)
print("\n📝 执行结果汇总:")
for summary in all_summaries:
print(f" {summary}")
print("\n✅ 脚本执行完成!")
print("="*60)
if __name__ == "__main__":
main()