mirror of
https://github.com/cc892786825/qiandao.git
synced 2025-12-16 23:10:01 +08:00
569 lines
23 KiB
Python
569 lines
23 KiB
Python
# 账号变量 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()
|