Files
cc892786825-qiandao/ChinaunicomSuperMaker.py
2025-12-07 07:01:18 +08:00

569 lines
23 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 账号变量 Chinaunicom = 手机号#online_token#appid
import os
import io
import re
import sys
import time
import json
import base64
import random
import logging
import binascii
import requests
import threading
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from typing import Optional
from notify import send
from threading import Event
from collections import deque
from datetime import datetime
from datetime import datetime, timedelta
from prettytable import PrettyTable
from typing import List, Optional
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from urllib.parse import urlparse, parse_qs
from requests.exceptions import ReadTimeout
from requests.exceptions import RequestException, ConnectionError, Timeout, HTTPError
from urllib3.exceptions import NameResolutionError, NewConnectionError
from requests.exceptions import RequestException, HTTPError
from urllib3.exceptions import NewConnectionError, MaxRetryError, NameResolutionError
GrantPrize = False # 权益超市自动领奖启用True/禁用False
#=================================================
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(message)s'
)
class MillisecondFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
if datefmt is None:
datefmt = "%Y-%m-%d %H:%M:%S.%f"
dt = datetime.fromtimestamp(record.created)
s = dt.strftime(datefmt)
return s[:-3] # 毫秒精度
# 应用毫秒格式到控制台 handler
console_handler = logging.getLogger().handlers[0]
console_handler.setFormatter(MillisecondFormatter('[%(asctime)s] %(message)s'))
# 线程安全封装打印
def log_with_time(message: str, proxy: Optional[str] = None):
if proxy:
message = f"[代理:{proxy}] {message}"
logging.info(message)
shared_session = requests.Session()
adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100, max_retries=Retry(total=3, backoff_factor=0.3))
shared_session.mount("http://", adapter)
shared_session.mount("https://", adapter)
# 代理类
class ProxyManager:
def __init__(self, get_proxy_func, limit=10):
self.get_proxy_func = get_proxy_func
self.limit = limit
self.request_count = 0
self.current_proxy = self.get_proxy_func()
self.lock = threading.Lock()
self.recent_proxies = deque(maxlen=5)
def get_proxy(self):
with self.lock:
if self.current_proxy is None:
return None
if self.request_count >= self.limit:
self.switch_proxy()
proxy_to_use = self.current_proxy
self.request_count += 1
return {"http": f"http://{proxy_to_use}", "https": f"http://{proxy_to_use}"}
def switch_proxy(self):
old = self.current_proxy
new_proxy = None
for _ in range(5):
candidate = self.get_proxy_func()
if candidate and candidate not in self.recent_proxies:
new_proxy = candidate
break
time.sleep(0.1)
if not new_proxy:
new_proxy = self.get_proxy_func()
self.recent_proxies.append(new_proxy)
self.current_proxy = new_proxy
self.request_count = 0
if self.current_proxy:
log_with_time(f"🔁 切换代理:{old} ➡️ {self.current_proxy}")
# 提取代理IP
def get_proxyIP(max_retries=3):
proxy_url = os.getenv("ProxyIP")
if not proxy_url:
return None
for attempt in range(max_retries):
try:
response = requests.get(proxy_url, timeout=5)
proxy_ip = response.text.strip()
if re.match(r'^\d+\.\d+\.\d+\.\d+:\d+$', proxy_ip):
return proxy_ip
res = response.json()
if res.get('code') == -1:
print(f"[代理异常] {res.get('message', '未知错误')}")
return None
except Exception as e:
print(f"[提取代理失败] 第 {attempt + 1} 次重试: {e}")
time.sleep(1)
return None
class ChinaunicomAPI:
def __init__(self, account_list: List[str]):
self.GrantPrize = GrantPrize
self.phone_list = []
self.online: List[bool] = []
self.appid: List[Optional[str]] = []
self.user_data: List[Optional[dict]] = []
self.proxies = {}
for entry in account_list:
entry = entry.strip()
if not entry:
continue
parts = entry.split('#')
if len(parts) == 1:
self.phone_list.append(parts[0])
elif len(parts) == 3:
self.phone_list.append(parts[0])
self.online.append(parts[1])
self.appid.append(parts[2])
for phone in self.phone_list:
masked_phone = f"{phone[:3]}****{phone[-4:]}"
self.proxies[masked_phone] = ProxyManager(get_proxyIP)
# 请求头封装
def get_headers(self, Isheaders=None):
if Isheaders == 1:
headers={
"User-Agent": "Mozilla/5.0 (Linux; Android 11; Redmi Note 10 Pro Build/RP1A.201005.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.159 Mobile Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"sec-ch-ua": '"Android WebView";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
"accesstoken": "ODZERTZCMjA1NTg1MTFFNDNFMThDRDYw",
"Content-Type": "application/json;charset=UTF-8",
"Origin": "https://10010.woread.com.cn",
"X-Requested-With": "com.sinovatech.unicom.ui",
"Referer": "https://10010.woread.com.cn/ng_woread/",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
}
elif Isheaders == 2:
headers={
'User-Agent': "Dalvik/2.1.0 (Linux; U; Android 12; leijun Pro Build/SKQ1.22013.001);unicom{version:android@11.0702}",
'Connection': "Keep-Alive",
'Accept-Encoding': "gzip"
}
return headers
# 请求封装
def do_send(self, url: str, method: str = "GET", data: Optional[dict] = None, headers: Optional[dict] = None, timeout: float = 10, max_retries: int = 3, show_resp: bool = False, proxy_manager: Optional[ProxyManager] = None, allow_redirects: bool = True) -> requests.Response:
for attempt in range(1, max_retries + 1):
try:
proxies = proxy_manager.get_proxy() if proxy_manager else None
if method.upper() == "GET":
if data:
params = data
resp = shared_session.get(url, params=params, headers=headers, timeout=timeout, proxies=proxies, allow_redirects=allow_redirects)
else:
resp = shared_session.get(url, headers=headers, timeout=timeout, proxies=proxies, allow_redirects=allow_redirects)
else:
if data and isinstance(data, dict):
if "token_online" in data:
resp = shared_session.request(method=method.upper(), url=url, data=data, headers=headers, timeout=timeout, proxies=proxies, allow_redirects=allow_redirects)
else:
resp = shared_session.request(method=method.upper(), url=url, json=data, headers=headers, timeout=timeout, proxies=proxies, allow_redirects=allow_redirects)
else:
resp = shared_session.request(method=method.upper(), url=url, headers=headers, timeout=timeout, proxies=proxies, allow_redirects=allow_redirects)
if show_resp:
print(f"[Response][{resp.status_code}] {resp.text}")
resp.raise_for_status()
if resp.status_code == 302:
return resp
else:
return resp.json()
except requests.exceptions.HTTPError as e:
raise
except ConnectionError as e:
if isinstance(e.args[0], NewConnectionError):
#print(f"🔴 :无法建立新连接: {e.args[0]}")
print(f"🔴 连接失败,第{attempt}次重试")
except requests.exceptions.ConnectionError as e:
if hasattr(e, 'args') and len(e.args) > 0 and isinstance(e.args[0], NameResolutionError):
print(f"🔴 DNS解析失败: 第{attempt}次重试")
except ReadTimeout as e:
print(f"🔴 请求超时,第{attempt}次重试")
except requests.exceptions.RequestException as e:
print(f"🔴 请求失败,第{attempt} 次重试: {e}")
if attempt == max_retries:
print("🔴 已达最大重试次数")
raise
# 登录
def login(self, phone: str, masked_phone: str, scene: str = "readzone", token_online: str = None, appid: str = None):
try:
if scene == "readzone":
pass
elif scene == "market":
if self.online and self.appid:
url = "https://m.client.10010.com/mobileService/onLine.htm"
headers = self.get_headers(Isheaders=2)
data = {
"isFirstInstall": "1",
"reqtime": str(int(time.time() * 1000)),
"netWay": "Wifi",
"version": "android@11.0000",
"token_online": token_online,
"provinceChanel": "general",
"appId": appid,
"deviceModel": "23013RK75C",
"step": "welcom",
"androidId": "caaa7b5f2b58b3eb",
"deviceBrand": "Xiaomi",
"flushkey": "1"
}
resp = self.do_send(url, method="POST", data=data, headers=headers, show_resp=False)
ecs_token = resp.get("ecs_token")
if ecs_token:
print(f"{masked_phone}")
return ecs_token
except Exception as e:
print(f"{masked_phone}登录异常: {str(e)}")
return None
# 获取Ticket
def get_ticket(self):
url = "https://m.client.10010.com/mobileService/openPlatform/openPlatLineNew.htm?to_url=https://contact.bol.wo.cn/market"
headers = self.get_headers(Isheaders=2)
try:
resp = self.do_send(url, method="GET", headers=headers, allow_redirects=False, show_resp=False)
if resp.status_code == 302:
location = resp.headers.get("Location", "")
parsed_url = urlparse(location)
query_params = parse_qs(parsed_url.query)
ticket_list = query_params.get("ticket")
ticket = ticket_list[0]
if ticket:
return ticket
except Exception as e:
print(f"获取Ticket异常: {str(e)}")
return None
def get_userToken(self, ticket):
url = f"https://backward.bol.wo.cn/prod-api/auth/marketUnicomLogin?ticket={ticket}"
headers = self.get_headers(Isheaders=2)
try:
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
userToken = resp.get("data", {}).get("token")
if userToken:
#print(f"{userToken}")
return userToken
except Exception as e:
print(f"获取userToken异常: {str(e)}")
return None
# 权益超市&任务列表
def get_AllActivityTasks(self, ecs_token, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/activityTask/getAllActivityTasks?activityId=12"
headers = self.get_headers(Isheaders=2)
headers['Cookie'] = 'ecs_token='+ ecs_token
headers['Authorization'] = 'Bearer '+ userToken
shareList = []
try:
resp = self.do_send(url, method="GET", headers=headers, show_resp=False)
active_id_listarr = resp.get("data", {})
for item in active_id_listarr.get("activityTaskUserDetailVOList", []):
share_info = {
"param": item.get("param1"),
"activityId": item.get("activityId"),
"name": item.get("name"),
"triggerTime": item.get("triggerTime"),
"triggeredTime": item.get("triggeredTime")
}
shareList.append(share_info)
return shareList
except Exception as e:
print(f"❌ 权益超市查询任务异常: {str(e)}")
return None
# 权益超市&任务执行
def do_ShareList(self, shareList, userToken):
try:
for task in shareList:
share_name = task.get("name")
share_param = task.get("param")
target_count = int(task.get("triggerTime", 1))
current_count = int(task.get("triggeredTime", 0))
if ("购买" in share_name or "秒杀" in share_name):
print(f"🚫 {share_name} [PASS]")
continue
if current_count >= target_count:
print(f"{share_name}")
continue
url = ""
if share_param:
if "浏览" in share_name or "查看" in share_name:
url = f"https://backward.bol.wo.cn/prod-api/promotion/activityTaskShare/checkView?checkKey={share_param}"
elif "分享" in share_name:
url = f"https://backward.bol.wo.cn/prod-api/promotion/activityTaskShare/checkShare?checkKey={share_param}"
if url:
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
if resp and resp.get("code") == 200:
print(f"{share_name} 执行成功")
except Exception as e:
print(f"❌ 权益超市{share_name}执行异常: {str(e)}")
# 抽奖池子
def get_Raffle(self, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/prizeList?id=12"
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
try:
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
keywords = ['月卡', '月会员', '月度', 'VIP月', '一个月']
live_prizes = []
if 'data' in resp and isinstance(resp['data'], list):
for prize in resp['data']:
name = prize.get('name', '')
if not any(kw in name for kw in keywords):
continue
try:
daily_limit = int(prize.get('dailyPrizeLimit', 0))
quantity = int(prize.get('quantity', 0))
prob = float(prize.get('probability', 0))
except:
daily_limit = 0
quantity = 0
prob = 0.0
if daily_limit > 0 and quantity > 0:
live_prizes.append({
'name': name,
'daily': daily_limit,
'total': quantity,
'prob': prob
})
if live_prizes:
print("📢 当前已放水!可抽有库存奖品👇👇👇")
for item in live_prizes:
print(f" {item['name']}")
print(f" └─ 今日投放: {item['daily']} | 总库存: {item['total']} | 概率: {item['prob'] * 100:.1f}%")
return True
else:
print("📢 当前未放水!终止抽奖😡😡😡")
return False
except Exception as e:
print(f"❌ 权益超市抽奖查询异常: {str(e)}")
return False
# 权益超市&抽奖次数查询
def get_raffle_count(self, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/getUserRaffleCount?id=12"
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
try:
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
count = resp.get("data", 0)
print(f"✅ 当前抽奖次数:{count}")
while count > 0:
print(f"🎯 第{abs(count - resp.get('data', 0)) + 1}次抽奖")
success = self.get_userRaffle(userToken)
if not success:
break
count -= 1
print(f"剩余抽奖次数: {count}")
except Exception as e:
print(f"❌ 权益超市查询抽奖次数异常: {str(e)}")
# 权益超市&抽奖
def get_userRaffle(self, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/userRaffle?id=12&channel="
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
try:
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
if resp.get("code") == 200:
if resp.get("data"):
lotteryRecordId = resp.get("data").get("lotteryRecordId")
prizesName = resp.get("data").get("prizesName")
message = resp.get("data").get("message")
if prizesName:
print(f"✅ 抽奖成功 {prizesName}")
else:
print(f"⚠️ 抽奖成功,但是{message}")
if self.GrantPrize:
print(f"✅ 已配置自动领奖")
self.get_grantPrize(userToken, lotteryRecordId, prizesName)
return True
if resp.get("code") == 500:
return self.get_validateCaptcha(userToken)
except Exception as e:
print(f"❌ 权益超市抽奖异常: {str(e)}")
return False
# 权益超市&人机验证
def get_validateCaptcha(self, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/validateCaptcha?id=12"
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
try:
resp = self.do_send(url, method="POST", headers=headers, show_resp=False)
if resp.get("code") == 200:
return self.get_userRaffle(userToken)
except Exception as e:
print(f"❌ 权益超市人机验证异常: {str(e)}")
return False
# 待领奖品
def get_MyPrize(self, userToken):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/getMyPrize"
headers = self.get_headers(Isheaders=2)
headers['Authorization'] = 'Bearer '+ userToken
data ={
"id": 12,
"type": 0,
"page": 1,
"limit": 100
}
try:
resp = self.do_send(url, method="POST", data=data, headers=headers, show_resp=False)
lists = resp.get("data", {}).get("list", [])
table = PrettyTable()
lottery_record_ids = []
if lists:
table.title = f"开始统计未领取奖品信息"
table.field_names = ["商品名称", "商品id", "获得时间", "失效时间"]
for item in lists:
lotteryRecordId = item.get("id")
prizesName =item.get("prizesName")
createTime =item.get("createTime")
deadline =item.get("deadline")
table.add_row([item.get("prizesName"), lotteryRecordId, createTime, deadline])
lottery_record_ids.append((lotteryRecordId, prizesName))
print(table)
if self.GrantPrize:
print(f"✅ 已配置自动领奖")
for lottery_id, prizesName in lottery_record_ids:
lotteryRecordId = lottery_id
self.get_grantPrize(userToken, lotteryRecordId, prizesName) # 调用领奖
except Exception as e:
print(f"❌ 权益超市待领奖品查询异常: {str(e)}")
return None
# 权益超市&领奖
def get_grantPrize(self, userToken, lotteryRecordId, prizesName):
url = "https://backward.bol.wo.cn/prod-api/promotion/home/raffleActivity/grantPrize?activityId=12"
headers = self.get_headers(Isheaders=2)
headers['Accept'] = "application/json, text/plain, */*"
headers['Accept-Encoding'] = "gzip, deflate, br, zstd"
headers['Content-Type'] = "application/json"
headers['Authorization'] = 'Bearer '+ userToken
data ={
"recordId": lotteryRecordId
}
try:
resp = self.do_send(url, method="POST", data=data, headers=headers, show_resp=False)
if resp.get("code") == 200:
print(f"{prizesName}领取成功")
except Exception as e:
print(f"❌ 权益超市领奖异常: {str(e)}")
return None
def QYCS_task(self, phone: str, appid: str):
index = self.phone_list.index(phone)
token_online = self.online[index]
masked_phone = f"{phone[:3]}****{phone[-4:]}"
ecs_token = self.login(phone=phone,masked_phone=masked_phone,scene="market", token_online=token_online,appid=appid)
if ecs_token:
ticket = self.get_ticket()
if ticket:
userToken = self.get_userToken(ticket)
if ecs_token and userToken:
shareList = self.get_AllActivityTasks(ecs_token, userToken)
if shareList:
self.do_ShareList(shareList, userToken)
if self.get_Raffle(userToken):
self.get_raffle_count(userToken)
self.get_MyPrize(userToken)
# 主程序
def TASK(self):
has_qycs_task = any(need_sync and appid for need_sync, appid in zip(self.online, self.appid))
if has_qycs_task:
account_num = 1
for phone, need_sync, appid in zip(self.phone_list, self.online, self.appid):
if need_sync and appid:
print(f"\n========== 第{account_num}个账号 ==========")
self.QYCS_task(phone, appid)
account_num += 1
if __name__ == "__main__":
raw = os.getenv("Chinaunicom", "").strip()
if not raw:
print("❌ 未检测到 Chinaunicom 环境变量")
sys.exit(1)
account_list = [line for line in raw.splitlines() if line.strip()]
api = ChinaunicomAPI(account_list)
print(f"✅ 检测到{len(api.phone_list)}个账号")
api.TASK()