更新 Points_Based/D_SFSY_SJBHD.py
This commit is contained in:
@@ -1,856 +0,0 @@
|
||||
# cron: 19 12,20 * * *
|
||||
# const $ = new Env("顺丰33周年庆活动");
|
||||
"""
|
||||
|
||||
顺丰33周年活动 - 完成任务抽勋章
|
||||
Author: 爱学习的呆子
|
||||
Version: 1.1.0
|
||||
Date: 2026-03-19
|
||||
|
||||
顺丰暗号环境变量:sfsyah
|
||||
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any
|
||||
from urllib.parse import unquote
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from threading import Lock
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
# 禁用SSL警告
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
inviteId = ['C6E5C3BDD7624520AB869D2AF9E75D95']
|
||||
|
||||
# ==================== 配置常量 ====================
|
||||
PROXY_TIMEOUT = 15
|
||||
MAX_PROXY_RETRIES = 5
|
||||
REQUEST_RETRY_COUNT = 3
|
||||
CONCURRENT_NUM = int(os.getenv('SFBF', '1'))
|
||||
if CONCURRENT_NUM > 20:
|
||||
CONCURRENT_NUM = 20
|
||||
elif CONCURRENT_NUM < 1:
|
||||
CONCURRENT_NUM = 1
|
||||
|
||||
print_lock = Lock()
|
||||
|
||||
# 周年活动配置
|
||||
ACTIVITY_CODE = "ANNIVERSARY_2026"
|
||||
TOKEN = 'wwesldfs29aniversaryvdld29'
|
||||
SYS_CODE = 'MCS-MIMP-CORE'
|
||||
|
||||
# 暗号环境变量(可选,不设置则自动从API获取答案)
|
||||
GUESS_ANSWER_ENV = 'sfsyah'
|
||||
|
||||
# 需要跳过的任务类型(需要实际操作的)
|
||||
SKIP_TASK_TYPES = [
|
||||
'BUY_ADD_VALUE_SERVICE_PACKET', # 通过服务套餐寄件
|
||||
'SEND_INTERNATIONAL_PACKAGE', # 寄一单国际件
|
||||
'LOOK_BIG_PACKAGE_GET_CASH', # 寄大件重货
|
||||
'SEND_SUCCESS_RECALL', # 去寄快递
|
||||
'CHARGE_NEW_EXPRESS_CARD', # 充值新速运通全国卡
|
||||
'CHARGE_COLLECT_ALL', # 充值1000元一键集齐
|
||||
'OPEN_FAMILY_HOME_MUTUAL', # 开通家庭8折互寄权益
|
||||
'BUY_ANNIVERSARY_LIMITED_PACKET', # 33周年宠粉券包
|
||||
# 'INTEGRAL_EXCHANGE', # 积分兑换抽勋章次数(已特殊处理)
|
||||
]
|
||||
|
||||
|
||||
# ==================== 日志 ====================
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
self.messages: List[str] = []
|
||||
self.lock = Lock()
|
||||
|
||||
def _log(self, icon: str, msg: str):
|
||||
line = f"{icon} {msg}"
|
||||
with print_lock:
|
||||
print(line)
|
||||
with self.lock:
|
||||
self.messages.append(line)
|
||||
|
||||
def info(self, msg): self._log('📝', msg)
|
||||
def success(self, msg): self._log('✅', msg)
|
||||
def warning(self, msg): self._log('⚠️', msg)
|
||||
def error(self, msg): self._log('❌', msg)
|
||||
def task(self, msg): self._log('🎯', msg)
|
||||
def medal(self, msg): self._log('🏅', msg)
|
||||
|
||||
|
||||
# ==================== 代理管理器 ====================
|
||||
class ProxyManager:
|
||||
def __init__(self, api_url: str):
|
||||
self.api_url = api_url
|
||||
|
||||
def get_proxy(self) -> Optional[Dict[str, str]]:
|
||||
try:
|
||||
if not self.api_url:
|
||||
return None
|
||||
response = requests.get(self.api_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
proxy_text = response.text.strip()
|
||||
if ':' in proxy_text:
|
||||
proxy = proxy_text if proxy_text.startswith('http') else f'http://{proxy_text}'
|
||||
# 隐藏认证信息用于显示
|
||||
display = proxy
|
||||
if '@' in proxy:
|
||||
parts = proxy.split('@')
|
||||
display = f"http://***:***@{parts[-1]}"
|
||||
with print_lock:
|
||||
print(f"✅ 获取代理: {display}")
|
||||
return {'http': proxy, 'https': proxy}
|
||||
with print_lock:
|
||||
print(f"❌ 获取代理失败: HTTP {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
with print_lock:
|
||||
print(f"❌ 获取代理异常: {str(e)[:100]}")
|
||||
return None
|
||||
|
||||
|
||||
# ==================== HTTP客户端 ====================
|
||||
class SFHttpClient:
|
||||
def __init__(self, proxy_manager: ProxyManager):
|
||||
self.proxy_manager = proxy_manager
|
||||
self.session = requests.Session()
|
||||
self.session.verify = False
|
||||
|
||||
proxy = self.proxy_manager.get_proxy()
|
||||
if proxy:
|
||||
self.session.proxies = proxy
|
||||
else:
|
||||
if self.proxy_manager.api_url:
|
||||
with print_lock:
|
||||
print("⚠️ 代理获取失败,将不使用代理")
|
||||
|
||||
self.headers = {
|
||||
'Host': 'mcs-mimp-web.sf-express.com',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
'channel': 'xcxpart',
|
||||
'platform': 'MINI_PROGRAM',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
}
|
||||
|
||||
def _generate_sign(self) -> Dict[str, str]:
|
||||
timestamp = str(int(round(time.time() * 1000)))
|
||||
data = f'token={TOKEN}×tamp={timestamp}&sysCode={SYS_CODE}'
|
||||
signature = hashlib.md5(data.encode()).hexdigest()
|
||||
return {
|
||||
'syscode': SYS_CODE,
|
||||
'timestamp': timestamp,
|
||||
'signature': signature,
|
||||
}
|
||||
|
||||
def request(self, url: str, data: Optional[Dict] = None, method: str = 'POST') -> Optional[Dict]:
|
||||
retry_count = 0
|
||||
proxy_retry_count = 0
|
||||
|
||||
while proxy_retry_count < MAX_PROXY_RETRIES:
|
||||
# 每次请求重新生成签名
|
||||
sign_data = self._generate_sign()
|
||||
headers = {**self.headers, **sign_data}
|
||||
|
||||
try:
|
||||
if method == 'POST':
|
||||
resp = self.session.post(url, headers=headers, json=data or {}, timeout=PROXY_TIMEOUT)
|
||||
else:
|
||||
resp = self.session.get(url, headers=headers, timeout=PROXY_TIMEOUT)
|
||||
resp.raise_for_status()
|
||||
|
||||
try:
|
||||
result = resp.json()
|
||||
if result is None:
|
||||
retry_count += 1
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
retry_count += 1
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
retry_count += 1
|
||||
error_str = str(e)
|
||||
|
||||
# 代理相关错误,切换代理
|
||||
if 'ProxyError' in error_str or 'SSLError' in error_str or 'ConnectionError' in error_str:
|
||||
proxy_retry_count += 1
|
||||
if proxy_retry_count < MAX_PROXY_RETRIES:
|
||||
new_proxy = self.proxy_manager.get_proxy()
|
||||
if new_proxy:
|
||||
self.session.proxies = new_proxy
|
||||
retry_count = 0 # 换代理后重置请求重试计数
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
# 普通请求错误,重试
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def login(self, url: str) -> tuple:
|
||||
"""登录(兼容URL和CK格式)"""
|
||||
try:
|
||||
decoded_input = unquote(url)
|
||||
if decoded_input.startswith('sessionId=') or '_login_mobile_=' in decoded_input:
|
||||
cookie_dict = {}
|
||||
for item in decoded_input.split(';'):
|
||||
item = item.strip()
|
||||
if '=' in item:
|
||||
k, v = item.split('=', 1)
|
||||
cookie_dict[k] = v
|
||||
for k, v in cookie_dict.items():
|
||||
self.session.cookies.set(k, v, domain='mcs-mimp-web.sf-express.com')
|
||||
user_id = cookie_dict.get('_login_user_id_', '')
|
||||
phone = cookie_dict.get('_login_mobile_', '')
|
||||
return (True, user_id, phone) if phone else (False, '', '')
|
||||
else:
|
||||
self.session.get(unquote(url), headers=self.headers, timeout=PROXY_TIMEOUT)
|
||||
cookies = self.session.cookies.get_dict()
|
||||
user_id = cookies.get('_login_user_id_', '')
|
||||
phone = cookies.get('_login_mobile_', '')
|
||||
return (True, user_id, phone) if phone else (False, '', '')
|
||||
except Exception as e:
|
||||
print(f'登录异常: {str(e)}')
|
||||
return False, '', ''
|
||||
|
||||
|
||||
# ==================== 周年活动任务执行器 ====================
|
||||
class AnniversaryExecutor:
|
||||
# MODIFIED: 增加 user_id 参数
|
||||
def __init__(self, http: SFHttpClient, logger: Logger, user_id: str):
|
||||
self.http = http
|
||||
self.logger = logger
|
||||
self.user_id = user_id
|
||||
|
||||
# ---------- API 方法 ----------
|
||||
|
||||
def get_activity_index(self) -> Optional[Dict]:
|
||||
"""获取周年活动首页信息"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026IndexService~index'
|
||||
resp = self.http.request(url, data={})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def get_task_list(self) -> Optional[List[Dict]]:
|
||||
"""获取周年活动任务列表"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activityTaskService~taskList'
|
||||
data = {
|
||||
"activityCode": ACTIVITY_CODE,
|
||||
"channelType": "MINI_PROGRAM"
|
||||
}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', [])
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.error(f'获取任务列表失败: {error_msg}')
|
||||
return None
|
||||
|
||||
def finish_task(self, task_code: str) -> bool:
|
||||
"""完成任务(浏览类)"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonRoutePost/memberEs/taskRecord/finishTask'
|
||||
data = {"taskCode": task_code}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
return True
|
||||
return False
|
||||
|
||||
# ========== 新增:专用接口-领取寄件会员权益 ==========
|
||||
def receive_vip_benefit(self) -> bool:
|
||||
"""领取寄件会员权益(特殊任务)"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberManage~memberEquity~commonEquityReceive'
|
||||
data = {"key": "surprise_benefit"}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
self.logger.success(f'[领取寄件会员权益] 完成成功')
|
||||
# 可选:解析返回的奖励信息
|
||||
if resp.get('obj'):
|
||||
self.logger.info(f'领取详情: {resp["obj"]}')
|
||||
return True
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'[领取寄件会员权益] 完成失败: {error_msg}')
|
||||
return False
|
||||
# =================================================
|
||||
|
||||
def fetch_tasks_reward(self) -> Optional[Dict]:
|
||||
"""领取已完成任务的奖励(获得抽勋章次数)"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026TaskService~fetchTasksReward'
|
||||
data = {
|
||||
"channelType": "MINI_PROGRAM",
|
||||
"activityCode": ACTIVITY_CODE
|
||||
}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'领取任务奖励失败: {error_msg}')
|
||||
return None
|
||||
|
||||
def get_card_status(self) -> Optional[Dict]:
|
||||
"""查看勋章收集状态"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026CardService~cardStatus'
|
||||
resp = self.http.request(url, data={})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def claim_medal(self, batch: bool = False) -> Optional[Dict]:
|
||||
"""抽勋章"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026CardService~claim'
|
||||
data = {"batchClaim": batch}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.error(f'抽勋章失败: {error_msg}')
|
||||
return None
|
||||
|
||||
def give_countdown_chance(self) -> Optional[Dict]:
|
||||
"""领取倒计时奖励(每日一次免费抽勋章机会)"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026CardService~giveClaimChance'
|
||||
resp = self.http.request(url, data={})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'领取倒计时奖励失败: {error_msg}')
|
||||
return None
|
||||
|
||||
def get_user_rest_integral(self) -> int:
|
||||
"""查询用户剩余积分"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activityTaskService~getUserRestIntegral'
|
||||
resp = self.http.request(url, data={})
|
||||
if resp and resp.get('success'):
|
||||
points = resp.get('obj', 0)
|
||||
self.logger.info(f'当前可用积分: {points}')
|
||||
return points
|
||||
return 0
|
||||
|
||||
def exchange_points_for_chance(self) -> bool:
|
||||
"""积分兑换抽勋章次数(10积分兑1次)"""
|
||||
points = self.get_user_rest_integral()
|
||||
if points < 10:
|
||||
self.logger.warning(f'积分不足10,无法兑换(当前: {points})')
|
||||
return False
|
||||
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026TaskService~integralExchange'
|
||||
data = {
|
||||
"exchangeNum": 1, # 一次兑换1次机会
|
||||
"activityCode": ACTIVITY_CODE # 当前活动码
|
||||
}
|
||||
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
self.logger.success('积分兑换成功,获得1次抽勋章次数')
|
||||
return True
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'积分兑换失败: {error_msg}')
|
||||
return False
|
||||
|
||||
def get_guess_title_list(self) -> Optional[Dict]:
|
||||
"""获取对暗号题目列表(含答案)"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026GuessService~titleList'
|
||||
resp = self.http.request(url, data={})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def submit_guess_answer(self, answer: str, period: str = '') -> Optional[Dict]:
|
||||
"""提交暗号答案,返回完整响应"""
|
||||
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026GuessService~answer'
|
||||
data = {"answerInfo": answer}
|
||||
if period:
|
||||
data["period"] = period
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp:
|
||||
self.logger.info(f'[对暗号] 提交响应: {json.dumps(resp, ensure_ascii=False)[:300]}')
|
||||
if resp.get('success'):
|
||||
obj = resp.get('obj', {})
|
||||
# 检查obj中是否有失败标志
|
||||
if isinstance(obj, dict) and obj.get('answerStatus') == 0:
|
||||
self.logger.warning(f'[对暗号] 答案错误')
|
||||
return None
|
||||
return obj
|
||||
else:
|
||||
error_msg = resp.get('errorMessage', '未知错误')
|
||||
self.logger.warning(f'提交暗号失败: {error_msg}')
|
||||
return None
|
||||
else:
|
||||
self.logger.warning(f'提交暗号失败: 请求失败')
|
||||
return None
|
||||
|
||||
# ---------- 新增:邀请初始化 ----------
|
||||
def do_invite(self):
|
||||
"""发送邀请初始化请求(借鉴自第二段代码)"""
|
||||
try:
|
||||
# 从全局 inviteId 列表中排除自己的 user_id
|
||||
available_invites = [inv for inv in inviteId if inv != self.user_id]
|
||||
if not available_invites:
|
||||
self.logger.warning("没有可用的邀请码(排除自身后为空),跳过邀请")
|
||||
return
|
||||
random_invite = random.choice(available_invites)
|
||||
# self.logger.info(f"随机选择邀请码: {random_invite} (排除自身 {self.user_id})")
|
||||
# 构造邀请请求 URL,使用 commonNoLoginPost
|
||||
url = "https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~anniversary2026IndexService~index"
|
||||
data = {"inviteType": 1, "inviteUserId": random_invite}
|
||||
resp = self.http.request(url, data=data)
|
||||
# if resp and resp.get('success'):
|
||||
# self.logger.success(f"邀请初始化成功,邀请码: {random_invite}")
|
||||
# else:
|
||||
# error_msg = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
# self.logger.warning(f"邀请初始化失败: {error_msg}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"邀请初始化异常: {str(e)}")
|
||||
|
||||
# ---------- 业务逻辑 ----------
|
||||
|
||||
def do_guess_game(self) -> bool:
|
||||
"""对暗号任务:读取所有日期的题目,按顺序匹配环境变量中的暗号逐日提交"""
|
||||
self.logger.task('[对暗号赢免单] 开始...')
|
||||
|
||||
# 1. 获取题目列表
|
||||
guess_info = self.get_guess_title_list()
|
||||
if not guess_info:
|
||||
self.logger.warning('[对暗号] 获取题目列表失败')
|
||||
return False
|
||||
|
||||
current_period = guess_info.get('currentPeriod', '')
|
||||
title_list = guess_info.get('guessTitleInfoList', [])
|
||||
|
||||
if not title_list:
|
||||
self.logger.warning('[对暗号] 题目列表为空')
|
||||
return False
|
||||
|
||||
# 按日期排序
|
||||
title_list.sort(key=lambda x: x.get('period', ''))
|
||||
|
||||
self.logger.info(f'[对暗号] 共 {len(title_list)} 天题目,当前日期: {current_period}')
|
||||
|
||||
# 2. 读取环境变量中的暗号列表(每行一个,按日期顺序对应)
|
||||
env_answers_raw = os.getenv(GUESS_ANSWER_ENV, '').strip()
|
||||
env_answers = [a.strip() for a in env_answers_raw.split('\n') if a.strip()] if env_answers_raw else []
|
||||
|
||||
if env_answers:
|
||||
self.logger.info(f'[对暗号] 环境变量已设置 {len(env_answers)} 个暗号')
|
||||
|
||||
# 3. 逐日处理
|
||||
any_success = False
|
||||
for i, title in enumerate(title_list):
|
||||
period = title.get('period', '')
|
||||
answer_status = title.get('answerStatus')
|
||||
tip = title.get('tip', '')
|
||||
|
||||
# 已答过的跳过
|
||||
if answer_status == 1:
|
||||
self.logger.success(f'[对暗号] {period} 已作答: {title.get("answerInfo", "")}')
|
||||
any_success = True
|
||||
continue
|
||||
|
||||
# 超过当前日期的不处理(未来题目)
|
||||
if period > current_period:
|
||||
self.logger.info(f'[对暗号] {period} 尚未开放,跳过')
|
||||
continue
|
||||
|
||||
# 获取答案:优先环境变量按顺序匹配,其次API返回的answerInfo
|
||||
answer = ''
|
||||
if i < len(env_answers):
|
||||
answer = env_answers[i]
|
||||
if not answer:
|
||||
answer = title.get('answerInfo', '')
|
||||
|
||||
if not answer:
|
||||
self.logger.warning(f'[对暗号] {period} 无法获取答案(提示: {tip})')
|
||||
self.logger.info(f'[对暗号] 请在环境变量 {GUESS_ANSWER_ENV} 第 {i + 1} 行填写该日暗号')
|
||||
continue
|
||||
|
||||
self.logger.info(f'[对暗号] {period} 提交答案: {answer}')
|
||||
|
||||
# 提交答案
|
||||
result = self.submit_guess_answer(answer, period)
|
||||
if result is not None:
|
||||
# 提交后重新获取题目列表验证是否真的成功
|
||||
time.sleep(1)
|
||||
verify_info = self.get_guess_title_list()
|
||||
if verify_info:
|
||||
for t in verify_info.get('guessTitleInfoList', []):
|
||||
if t.get('period') == period:
|
||||
if t.get('answerStatus') == 1:
|
||||
self.logger.success(f'[对暗号] {period} 验证通过,答案已录入: {t.get("answerInfo", "")}')
|
||||
any_success = True
|
||||
else:
|
||||
self.logger.warning(f'[对暗号] {period} 验证失败,答案未被采纳 (answerStatus={t.get("answerStatus")})')
|
||||
break
|
||||
else:
|
||||
self.logger.success(f'[对暗号] {period} 提交成功(未能验证)')
|
||||
any_success = True
|
||||
else:
|
||||
self.logger.warning(f'[对暗号] {period} 提交失败')
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
return any_success
|
||||
|
||||
def do_tasks(self, result: Dict) -> None:
|
||||
"""执行所有可自动完成的任务"""
|
||||
self.logger.info('正在获取周年活动任务列表...')
|
||||
tasks = self.get_task_list()
|
||||
if tasks is None:
|
||||
return
|
||||
|
||||
self.logger.info(f'共发现 {len(tasks)} 个任务')
|
||||
|
||||
for task in tasks:
|
||||
task_name = task.get('taskName', '未知')
|
||||
task_type = task.get('taskType', '')
|
||||
task_code = task.get('taskCode', '')
|
||||
status = task.get('status')
|
||||
process = task.get('process', '')
|
||||
rest_finish = task.get('restFinishTime', 0)
|
||||
virtual_token = task.get('virtualTokenNum', 0)
|
||||
|
||||
# status=3 表示已完成且已领奖,status=1且restFinishTime=0表示已完成待领奖
|
||||
if status == 3 or (status == 1 and rest_finish <= 0):
|
||||
can_receive = task.get('canReceiveTokenNum', 0)
|
||||
if can_receive > 0:
|
||||
self.logger.info(f'[{task_name}] 已完成,待领取 {can_receive} 次抽勋章机会')
|
||||
else:
|
||||
self.logger.success(f'[{task_name}] 已完成 ({process})')
|
||||
continue
|
||||
|
||||
if task_type in SKIP_TASK_TYPES:
|
||||
self.logger.info(f'[{task_name}] 需要实际操作,跳过')
|
||||
continue
|
||||
|
||||
# 对暗号单独处理
|
||||
if task_type == 'GUESS_GAME_TIP':
|
||||
if self.do_guess_game():
|
||||
result['tasks_completed'] += 1
|
||||
continue
|
||||
|
||||
# 积分兑换特殊处理
|
||||
if task_type == 'INTEGRAL_EXCHANGE':
|
||||
self.logger.task(f'[{task_name}] 尝试用积分兑换抽勋章次数...')
|
||||
if self.exchange_points_for_chance():
|
||||
self.logger.success(f'[{task_name}] 积分兑换成功')
|
||||
result['tasks_completed'] += 1
|
||||
continue
|
||||
|
||||
# ========== 新增:领取寄件会员权益特殊处理 ==========
|
||||
if task_type == 'RECEIVE_VIP_BENEFIT':
|
||||
self.logger.task(f'[{task_name}] 尝试专用接口领取...')
|
||||
if self.receive_vip_benefit():
|
||||
result['tasks_completed'] += 1
|
||||
continue
|
||||
# =================================================
|
||||
|
||||
# 有taskCode的任务尝试自动完成(通用接口)
|
||||
if task_code:
|
||||
self.logger.task(f'[{task_name}] 尝试完成任务 (taskCode: {task_code})')
|
||||
if self.finish_task(task_code):
|
||||
self.logger.success(f'[{task_name}] 完成成功,可获得 {virtual_token} 次抽勋章机会')
|
||||
result['tasks_completed'] += 1
|
||||
else:
|
||||
self.logger.warning(f'[{task_name}] 完成失败')
|
||||
time.sleep(1)
|
||||
else:
|
||||
self.logger.info(f'[{task_name}] 无taskCode,跳过 ({task_type})')
|
||||
|
||||
def do_fetch_rewards(self, result: Dict) -> None:
|
||||
"""领取所有已完成任务的奖励"""
|
||||
self.logger.info('领取任务奖励...')
|
||||
time.sleep(1)
|
||||
reward_resp = self.fetch_tasks_reward()
|
||||
if reward_resp:
|
||||
received = reward_resp.get('receivedAccountList', [])
|
||||
if received:
|
||||
for item in received:
|
||||
currency = item.get('currency', '')
|
||||
amount = item.get('amount', 0)
|
||||
task_type = item.get('taskType', '')
|
||||
self.logger.success(f'领取奖励: {currency} x{amount} (来自: {task_type})')
|
||||
else:
|
||||
self.logger.info('无新的任务奖励可领取')
|
||||
|
||||
# 显示累计进度
|
||||
accrued = reward_resp.get('accruedTaskAward', {})
|
||||
progress = accrued.get('currentProgress', 0)
|
||||
config = accrued.get('progressConfig', {})
|
||||
if config:
|
||||
milestones = ', '.join([f'{k}个任务得{v}次' for k, v in sorted(config.items(), key=lambda x: int(x[0]))])
|
||||
self.logger.info(f'累计完成任务数: {progress} (里程碑: {milestones})')
|
||||
|
||||
def do_countdown_chance(self, result: Dict) -> None:
|
||||
"""领取倒计时奖励(每日免费抽勋章机会)"""
|
||||
self.logger.info('领取倒计时奖励...')
|
||||
time.sleep(1)
|
||||
resp = self.give_countdown_chance()
|
||||
if resp:
|
||||
if resp.get('todayCountdownChanceGiven'):
|
||||
received = resp.get('receivedAccountList', [])
|
||||
if received:
|
||||
for item in received:
|
||||
currency = item.get('currency', '')
|
||||
amount = item.get('amount', 0)
|
||||
self.logger.success(f'倒计时奖励: {currency} x{amount}')
|
||||
else:
|
||||
self.logger.success('倒计时奖励已领取')
|
||||
else:
|
||||
self.logger.info('今日倒计时奖励未到领取时间')
|
||||
else:
|
||||
self.logger.warning('倒计时奖励领取失败')
|
||||
|
||||
|
||||
def do_claim_medals(self, result: Dict) -> None:
|
||||
"""抽勋章直到没有次数"""
|
||||
# 先查看当前状态
|
||||
card_status = self.get_card_status()
|
||||
if card_status:
|
||||
claim_balance = 0
|
||||
for acc in card_status.get('currentAccountList', []):
|
||||
if acc.get('currency') == 'CLAIM_CHANCE':
|
||||
claim_balance = acc.get('balance', 0)
|
||||
break
|
||||
self.logger.info(f'当前可抽勋章次数: {claim_balance}')
|
||||
|
||||
# 显示已有勋章
|
||||
medals_owned = []
|
||||
for acc in card_status.get('currentAccountList', []):
|
||||
currency = acc.get('currency', '')
|
||||
balance = acc.get('balance', 0)
|
||||
if currency != 'CLAIM_CHANCE' and balance > 0:
|
||||
medals_owned.append(f'{currency}x{balance}')
|
||||
if medals_owned:
|
||||
self.logger.info(f'已有勋章: {", ".join(medals_owned)}')
|
||||
|
||||
if claim_balance <= 0:
|
||||
self.logger.info('无抽勋章次数,跳过')
|
||||
return
|
||||
|
||||
# 开始抽勋章
|
||||
self.logger.info('开始抽勋章...')
|
||||
claim_count = 0
|
||||
max_claims = 30
|
||||
|
||||
while claim_count < max_claims:
|
||||
time.sleep(1)
|
||||
claim_result = self.claim_medal(batch=False)
|
||||
if claim_result is None:
|
||||
break
|
||||
|
||||
received = claim_result.get('receivedAccountList', [])
|
||||
current_accounts = claim_result.get('currentAccountList', [])
|
||||
|
||||
if received:
|
||||
for item in received:
|
||||
currency = item.get('currency', '未知')
|
||||
amount = item.get('amount', 0)
|
||||
self.logger.medal(f'抽到勋章: {currency} x{amount}')
|
||||
result['medals_detail'].append({'type': currency, 'amount': amount})
|
||||
result['medals_claimed'] += amount
|
||||
claim_count += 1
|
||||
else:
|
||||
self.logger.info('没有抽到勋章或无抽取次数')
|
||||
break
|
||||
|
||||
# 检查剩余次数
|
||||
claim_balance = 0
|
||||
for acc in current_accounts:
|
||||
if acc.get('currency') == 'CLAIM_CHANCE':
|
||||
claim_balance = acc.get('balance', 0)
|
||||
break
|
||||
|
||||
result['claim_balance'] = claim_balance
|
||||
self.logger.info(f'剩余抽取次数: {claim_balance}')
|
||||
|
||||
if claim_balance <= 0:
|
||||
break
|
||||
|
||||
self.logger.info(f'抽勋章完成,本次共抽取 {result["medals_claimed"]} 个勋章')
|
||||
|
||||
def run(self) -> Dict[str, Any]:
|
||||
"""执行周年活动全流程"""
|
||||
result = {
|
||||
'tasks_completed': 0,
|
||||
'medals_claimed': 0,
|
||||
'medals_detail': [],
|
||||
'claim_balance': 0,
|
||||
}
|
||||
|
||||
# MODIFIED: 在任务开始前先发送邀请
|
||||
self.do_invite()
|
||||
|
||||
# 0. 获取活动首页信息
|
||||
index_info = self.get_activity_index()
|
||||
if index_info:
|
||||
self.logger.info(f'活动时间: {index_info.get("acStartTime", "")} ~ {index_info.get("acEndTime", "")}')
|
||||
self.logger.info(f'历史寄件数: {index_info.get("sendNum", 0)},累计支付: {index_info.get("payAmount", 0)}元')
|
||||
|
||||
# 1. 执行任务(含对暗号)
|
||||
self.do_tasks(result)
|
||||
|
||||
# 2. 领取任务奖励(获得抽勋章次数)
|
||||
self.do_fetch_rewards(result)
|
||||
|
||||
# 3. 抽勋章
|
||||
self.do_claim_medals(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ==================== 账号执行 ====================
|
||||
def run_account(account_url: str, index: int) -> Dict[str, Any]:
|
||||
"""执行单个账号"""
|
||||
logger = Logger()
|
||||
proxy_url = os.getenv('SF_PROXY_API_URL', '')
|
||||
proxy_manager = ProxyManager(proxy_url)
|
||||
|
||||
# 登录
|
||||
http = SFHttpClient(proxy_manager)
|
||||
retry_count = 0
|
||||
login_success = False
|
||||
phone = ''
|
||||
user_id = ''
|
||||
|
||||
while retry_count < MAX_PROXY_RETRIES and not login_success:
|
||||
try:
|
||||
if retry_count > 0:
|
||||
http = SFHttpClient(proxy_manager)
|
||||
success, user_id, phone = http.login(account_url)
|
||||
if success:
|
||||
login_success = True
|
||||
break
|
||||
except Exception as e:
|
||||
pass
|
||||
retry_count += 1
|
||||
if retry_count < MAX_PROXY_RETRIES:
|
||||
time.sleep(2)
|
||||
|
||||
if not login_success:
|
||||
logger.error(f'账号{index + 1} 登录失败')
|
||||
return {'success': False, 'phone': '', 'index': index,
|
||||
'tasks_completed': 0, 'medals_claimed': 0, 'medals_detail': [], 'claim_balance': 0}
|
||||
|
||||
masked_phone = phone[:3] + "****" + phone[7:] if len(phone) >= 7 else phone
|
||||
logger.success(f'账号{index + 1}: 【{masked_phone}】登录成功')
|
||||
|
||||
# 随机延迟
|
||||
time.sleep(random.uniform(1, 3))
|
||||
|
||||
# MODIFIED: 将 user_id 传递给 AnniversaryExecutor
|
||||
executor = AnniversaryExecutor(http, logger, user_id)
|
||||
activity_result = executor.run()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'phone': phone,
|
||||
'index': index,
|
||||
**activity_result,
|
||||
}
|
||||
|
||||
|
||||
# ==================== 主程序 ====================
|
||||
def main():
|
||||
env_name = 'sfsyUrl'
|
||||
env_value = os.getenv(env_name)
|
||||
if not env_value:
|
||||
print(f"❌ 未找到环境变量 {env_name},请检查配置")
|
||||
return
|
||||
|
||||
account_urls = [url.strip() for url in env_value.split('&') if url.strip()]
|
||||
if not account_urls:
|
||||
print(f"❌ 环境变量 {env_name} 为空或格式错误")
|
||||
return
|
||||
|
||||
# 暗号提示
|
||||
guess_answer = os.getenv(GUESS_ANSWER_ENV, '')
|
||||
if guess_answer:
|
||||
answer_lines = [a.strip() for a in guess_answer.strip().split('\n') if a.strip()]
|
||||
print(f"🔑 已设置暗号环境变量,共 {len(answer_lines)} 个暗号(按日期顺序对应)")
|
||||
for i, a in enumerate(answer_lines):
|
||||
print(f" 第{i+1}天: {a}")
|
||||
else:
|
||||
print(f"🔑 未设置暗号环境变量({GUESS_ANSWER_ENV}),将自动从API获取答案")
|
||||
|
||||
random.shuffle(account_urls)
|
||||
|
||||
print("=" * 60)
|
||||
print(f"🎉 顺丰33周年活动 - 完成任务抽勋章")
|
||||
print(f"👨💻 作者: 爱学习的呆子")
|
||||
print(f"📱 共获取到 {len(account_urls)} 个账号")
|
||||
print(f"⚙️ 并发数量: {CONCURRENT_NUM}")
|
||||
print(f"⏰ 执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("=" * 60)
|
||||
|
||||
all_results = []
|
||||
|
||||
if CONCURRENT_NUM <= 1:
|
||||
for idx, url in enumerate(account_urls):
|
||||
result = run_account(url, idx)
|
||||
all_results.append(result)
|
||||
if idx < len(account_urls) - 1:
|
||||
print("-" * 60)
|
||||
time.sleep(2)
|
||||
else:
|
||||
with ThreadPoolExecutor(max_workers=CONCURRENT_NUM) as pool:
|
||||
futures = {pool.submit(run_account, url, idx): idx for idx, url in enumerate(account_urls)}
|
||||
for future in as_completed(futures):
|
||||
all_results.append(future.result())
|
||||
|
||||
all_results.sort(key=lambda x: x['index'])
|
||||
|
||||
# 汇总
|
||||
print(f"\n" + "=" * 70)
|
||||
print(f"📊 周年活动汇总")
|
||||
print("=" * 70)
|
||||
print(f"{'序号':<6} {'手机号':<15} {'完成任务数':<12} {'抽到勋章数':<12} {'勋章详情':<20}")
|
||||
print("-" * 70)
|
||||
|
||||
total_tasks = 0
|
||||
total_medals = 0
|
||||
|
||||
for r in all_results:
|
||||
idx = r['index'] + 1
|
||||
phone = r['phone'][:3] + "****" + r['phone'][7:] if r.get('phone') and len(r['phone']) >= 7 else r.get('phone', '未登录')
|
||||
tasks = r.get('tasks_completed', 0)
|
||||
medals = r.get('medals_claimed', 0)
|
||||
detail = ', '.join([f"{d['type']}x{d['amount']}" for d in r.get('medals_detail', [])]) or '无'
|
||||
status = "✅" if r['success'] else "❌"
|
||||
|
||||
total_tasks += tasks
|
||||
total_medals += medals
|
||||
|
||||
print(f"{idx:<6} {phone:<15} {tasks:<12} {medals:<12} {detail:<20} {status}")
|
||||
|
||||
print("-" * 70)
|
||||
print(f"{'汇总':<6} {'账号: ' + str(len(all_results)):<15} {total_tasks:<12} {total_medals:<12}")
|
||||
print("=" * 70)
|
||||
print("\n🎊 所有账号执行完成!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,816 @@
|
||||
# cron: 19 12,20 * * *
|
||||
# const $ = new Env("顺丰2026世界杯活动");
|
||||
"""
|
||||
|
||||
顺丰2026世界杯活动 - 射门游戏 + 比赛竞猜 + 每日礼物
|
||||
Author: Auto-generated (参考端午活动脚本)
|
||||
Version: 1.2.0
|
||||
Date: 2026-07-06
|
||||
|
||||
更新日志:
|
||||
1.2.0 (2026-07-06) 优化邀请逻辑:支持列表内账号互相邀请,显示已邀请好友列表
|
||||
1.1.0 (2026-07-04) 新增下注结算(settleBet)阶段、赔率显示、潜在收益/今日盈亏列
|
||||
1.0.0 (2026-07-03) 初版:每日礼物 + 任务(白名单) + 射门游戏 + 比赛下注
|
||||
|
||||
活动周期: 2026-07-02 ~ 2026-07-20
|
||||
货币: GOLD_COIN (金币)
|
||||
活动码: WORLD_CUP
|
||||
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any
|
||||
from urllib.parse import unquote
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from threading import Lock
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
inviteId = [] # 运行时从账号列表动态提取
|
||||
|
||||
PROXY_TIMEOUT = 15
|
||||
MAX_PROXY_RETRIES = 5
|
||||
REQUEST_RETRY_COUNT = 3
|
||||
CONCURRENT_NUM = int(os.getenv('SFBF', '1'))
|
||||
if CONCURRENT_NUM > 20:
|
||||
CONCURRENT_NUM = 20
|
||||
elif CONCURRENT_NUM < 1:
|
||||
CONCURRENT_NUM = 1
|
||||
|
||||
# 代理开关(默认启用)
|
||||
ENABLE_PROXY = os.getenv('ENABLE_PROXY', 'true').lower() == 'true'
|
||||
|
||||
print_lock = Lock()
|
||||
|
||||
ACTIVITY_CODE = "WORLD_CUP"
|
||||
TOKEN = 'wwesldfs29aniversaryvdld29'
|
||||
SYS_CODE = 'MCS-MIMP-CORE'
|
||||
|
||||
CURRENCY_NAMES = {'GOLD_COIN': '金币'}
|
||||
CHANNEL = '26sjbapp'
|
||||
PLATFORM = 'SFAPP'
|
||||
CITY_CODE = '021'
|
||||
|
||||
# ===== 活动阶段开关(True=开启 / False=关闭)=====
|
||||
ENABLE_DAILY_GIFT = True # 每日礼物
|
||||
ENABLE_TASKS = True # 任务(仅执行 AUTO_FINISH_TASK_CODES 白名单里的)
|
||||
ENABLE_GAME = True # 射门游戏
|
||||
ENABLE_BET = True # 比赛竞猜(下注)
|
||||
ENABLE_SETTLE = True # 下注结算(早上跑完后,结算前一天已开赛的下注盈亏)
|
||||
|
||||
# ===== 下注参数 =====
|
||||
# 服务端最低下注金币为 10,< 10 会报 notSuccess。修改前请先抓包确认下限。
|
||||
BET_COIN = 10 # 每场比赛下注金币数
|
||||
|
||||
SKIP_TASK_TYPES = [
|
||||
'BUY_ADD_VALUE_SERVICE_PACKET',
|
||||
'SEND_INTERNATIONAL_PACKAGE',
|
||||
'LOOK_BIG_PACKAGE_GET_CASH',
|
||||
'SEND_SUCCESS_RECALL',
|
||||
'CHARGE_NEW_EXPRESS_CARD',
|
||||
'CHARGE_COLLECT_ALL',
|
||||
'OPEN_FAMILY_HOME_MUTUAL',
|
||||
'INVITEFRIENDS_PARTAKE_ACTIVITY',
|
||||
'BROWSE_INTEGRAL_PLANET',
|
||||
'BROWSE_FAMILY_HOME_MUTUAL',
|
||||
]
|
||||
# 浏览类任务一律跳过(含 BROWSE_/VIEWPAGE_ 前缀),不发起 finishTask
|
||||
SKIP_TASK_PREFIXES = ('BROWSE_', 'VIEWPAGE_')
|
||||
|
||||
# 仅对这些 taskCode 直接调用 finishTask
|
||||
AUTO_FINISH_TASK_CODES = {
|
||||
'0CD7AFA68009402DBE5BF9D3C10D0115', # 浏览积分商城(验证通过)
|
||||
'895011E183CE4E61BBACE8A63B98596A', # 去看看互寄8折权益(未验证,可移除)
|
||||
}
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
self.messages: List[str] = []
|
||||
self.lock = Lock()
|
||||
|
||||
def _log(self, icon: str, msg: str):
|
||||
line = f"{icon} {msg}"
|
||||
with print_lock:
|
||||
print(line)
|
||||
with self.lock:
|
||||
self.messages.append(line)
|
||||
|
||||
def info(self, msg): self._log('📝', msg)
|
||||
def success(self, msg): self._log('✅', msg)
|
||||
def warning(self, msg): self._log('⚠️', msg)
|
||||
def error(self, msg): self._log('❌', msg)
|
||||
def task(self, msg): self._log('🎯', msg)
|
||||
def goal(self, msg): self._log('⚽', msg)
|
||||
|
||||
|
||||
class ProxyManager:
|
||||
def __init__(self, api_url: str):
|
||||
self.api_url = api_url
|
||||
|
||||
def get_proxy(self) -> Optional[Dict[str, str]]:
|
||||
# 如果代理开关关闭,直接返回 None
|
||||
if not ENABLE_PROXY:
|
||||
return None
|
||||
try:
|
||||
if not self.api_url:
|
||||
return None
|
||||
response = requests.get(self.api_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
# 尝试解析 JSON
|
||||
try:
|
||||
data = response.json()
|
||||
if 'data' in data and 'list' in data['data'] and data['data']['list']:
|
||||
proxy_info = data['data']['list'][0]
|
||||
ip = proxy_info.get('ip')
|
||||
port = proxy_info.get('port')
|
||||
if ip and port:
|
||||
proxy = f'http://{ip}:{port}'
|
||||
with print_lock:
|
||||
print(f"✅ 获取代理: {proxy}")
|
||||
return {'http': proxy, 'https': proxy}
|
||||
except (json.JSONDecodeError, KeyError, IndexError):
|
||||
# 不是 JSON,按纯文本处理(兼容旧格式)
|
||||
proxy_text = response.text.strip()
|
||||
if ':' in proxy_text:
|
||||
proxy = proxy_text if proxy_text.startswith('http') else f'http://{proxy_text}'
|
||||
display = proxy
|
||||
if '@' in proxy:
|
||||
parts = proxy.split('@')
|
||||
display = f"http://***:***@{parts[-1]}"
|
||||
with print_lock:
|
||||
print(f"✅ 获取代理: {display}")
|
||||
return {'http': proxy, 'https': proxy}
|
||||
with print_lock:
|
||||
print(f"❌ 获取代理失败: 无法解析代理数据")
|
||||
return None
|
||||
with print_lock:
|
||||
print(f"❌ 获取代理失败: HTTP {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
with print_lock:
|
||||
print(f"❌ 获取代理异常: {str(e)[:100]}")
|
||||
return None
|
||||
|
||||
|
||||
class SFHttpClient:
|
||||
def __init__(self, proxy_manager: ProxyManager):
|
||||
self.proxy_manager = proxy_manager
|
||||
self.session = requests.Session()
|
||||
self.session.verify = False
|
||||
|
||||
# 根据开关决定是否尝试获取代理
|
||||
if ENABLE_PROXY:
|
||||
proxy = self.proxy_manager.get_proxy()
|
||||
if proxy:
|
||||
self.session.proxies = proxy
|
||||
else:
|
||||
if self.proxy_manager.api_url:
|
||||
with print_lock:
|
||||
print("⚠️ 代理获取失败,将不使用代理")
|
||||
|
||||
self.headers = {
|
||||
'Host': 'mcs-mimp-web.sf-express.com',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 mediaCode=SFEXPRESSAPP-iOS-ML',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
'channel': CHANNEL,
|
||||
'platform': PLATFORM,
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
}
|
||||
|
||||
def _generate_sign(self) -> Dict[str, str]:
|
||||
timestamp = str(int(round(time.time() * 1000)))
|
||||
data = f'token={TOKEN}×tamp={timestamp}&sysCode={SYS_CODE}'
|
||||
signature = hashlib.md5(data.encode()).hexdigest()
|
||||
return {
|
||||
'sysCode': SYS_CODE,
|
||||
'timestamp': timestamp,
|
||||
'signature': signature,
|
||||
}
|
||||
|
||||
def request(self, url: str, data: Optional[Dict] = None, method: str = 'POST') -> Optional[Dict]:
|
||||
retry_count = 0
|
||||
max_proxy_retries = MAX_PROXY_RETRIES if ENABLE_PROXY else 1
|
||||
proxy_retry_count = 0
|
||||
|
||||
while proxy_retry_count < max_proxy_retries:
|
||||
sign_data = self._generate_sign()
|
||||
headers = {**self.headers, **sign_data}
|
||||
|
||||
try:
|
||||
if method == 'POST':
|
||||
resp = self.session.post(url, headers=headers, json=data or {}, timeout=PROXY_TIMEOUT)
|
||||
else:
|
||||
resp = self.session.get(url, headers=headers, timeout=PROXY_TIMEOUT)
|
||||
resp.raise_for_status()
|
||||
|
||||
try:
|
||||
result = resp.json()
|
||||
if result is None:
|
||||
retry_count += 1
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
retry_count += 1
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
retry_count += 1
|
||||
error_str = str(e)
|
||||
|
||||
if ENABLE_PROXY and ('ProxyError' in error_str or 'SSLError' in error_str or 'ConnectionError' in error_str):
|
||||
proxy_retry_count += 1
|
||||
if proxy_retry_count < MAX_PROXY_RETRIES:
|
||||
new_proxy = self.proxy_manager.get_proxy()
|
||||
if new_proxy:
|
||||
self.session.proxies = new_proxy
|
||||
retry_count = 0
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
if retry_count < REQUEST_RETRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def login(self, url: str) -> tuple:
|
||||
try:
|
||||
decoded_input = unquote(url)
|
||||
if decoded_input.startswith('sessionId=') or '_login_mobile_=' in decoded_input:
|
||||
cookie_dict = {}
|
||||
for item in decoded_input.split(';'):
|
||||
item = item.strip()
|
||||
if '=' in item:
|
||||
k, v = item.split('=', 1)
|
||||
cookie_dict[k] = v
|
||||
for k, v in cookie_dict.items():
|
||||
self.session.cookies.set(k, v, domain='mcs-mimp-web.sf-express.com')
|
||||
user_id = cookie_dict.get('_login_user_id_', '')
|
||||
phone = cookie_dict.get('_login_mobile_', '')
|
||||
return (True, user_id, phone) if phone else (False, '', '')
|
||||
else:
|
||||
self.session.get(unquote(url), headers=self.headers, timeout=PROXY_TIMEOUT)
|
||||
cookies = self.session.cookies.get_dict()
|
||||
user_id = cookies.get('_login_user_id_', '')
|
||||
phone = cookies.get('_login_mobile_', '')
|
||||
return (True, user_id, phone) if phone else (False, '', '')
|
||||
except Exception as e:
|
||||
print(f'登录异常: {str(e)}')
|
||||
return False, '', ''
|
||||
|
||||
|
||||
class WorldCupExecutor:
|
||||
BASE_URL = 'https://mcs-mimp-web.sf-express.com/mcs-mimp'
|
||||
|
||||
def __init__(self, http: SFHttpClient, logger: Logger, user_id: str, invite_pool: list = None):
|
||||
self.http = http
|
||||
self.logger = logger
|
||||
self.user_id = user_id
|
||||
self.invite_pool = invite_pool or []
|
||||
self.level_list: List[Dict] = []
|
||||
|
||||
def _post(self, path: str, data: Optional[Dict] = None) -> Optional[Dict]:
|
||||
url = f'{self.BASE_URL}{path}'
|
||||
return self.http.request(url, data=data or {})
|
||||
|
||||
def get_activity_index(self) -> Optional[Dict]:
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupIndexService~index')
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def get_game_index(self) -> Optional[Dict]:
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupGameService~index')
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def get_bet_status(self) -> Optional[Dict]:
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupMatchService~betStatus')
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def get_daily_gift_status(self) -> Optional[Dict]:
|
||||
data = {"cityCode": CITY_CODE}
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupDailyService~getDailyGiftStatus', data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def receive_daily_gift(self) -> Optional[Dict]:
|
||||
data = {"cityCode": CITY_CODE}
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupDailyService~receiveDailyGift', data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def get_task_list(self) -> Optional[List[Dict]]:
|
||||
data = {"activityCode": ACTIVITY_CODE, "channelType": PLATFORM}
|
||||
resp = self._post('/commonPost/~memberNonactivity~activityTaskService~taskList', data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', [])
|
||||
return None
|
||||
|
||||
def finish_task(self, task_code: str) -> bool:
|
||||
url = f'{self.BASE_URL}/commonPost/~memberEs~taskRecord~finishTask'
|
||||
data = {"taskCode": task_code}
|
||||
resp = self.http.request(url, data=data)
|
||||
return bool(resp and resp.get('success'))
|
||||
|
||||
def fetch_task_reward(self) -> Optional[Dict]:
|
||||
data = {"channelType": PLATFORM, "activityCode": ACTIVITY_CODE}
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupTaskService~fetchTaskReward', data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
return None
|
||||
|
||||
def report_pass(self, level: int, shot_num: int) -> Optional[Dict]:
|
||||
data = {"level": level, "shotNum": shot_num}
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupGameService~passReport', data)
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', {})
|
||||
err = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'第{level}关上报失败: {err}')
|
||||
return None
|
||||
|
||||
def place_bet(self, match_id: str, bet_result: int, bet_coin: int) -> bool:
|
||||
"""下注
|
||||
bet_result: 0=主胜 1=平 2=客胜 (与 odds 数组顺序一致)
|
||||
bet_coin: 下注金币数
|
||||
"""
|
||||
data = {"matchId": match_id, "betResult": bet_result, "betCoin": bet_coin}
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupMatchService~placeBet', data)
|
||||
if resp and resp.get('success'):
|
||||
return True
|
||||
err = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'下注失败(match={match_id}, result={bet_result}): {err}')
|
||||
return False
|
||||
|
||||
def settle_bet(self) -> Optional[list]:
|
||||
"""结算下注
|
||||
返回已开赛并结算的下注列表;空数组表示今天没有可结算的
|
||||
"""
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupMatchService~settleBet', {})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', []) or []
|
||||
err = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f'结算请求失败: {err}')
|
||||
return None
|
||||
|
||||
# ========== 邀请相关方法 ==========
|
||||
def do_invite(self):
|
||||
"""列表内账号互相邀请"""
|
||||
try:
|
||||
pool = self.invite_pool if self.invite_pool else inviteId
|
||||
available_invites = [inv for inv in pool if inv != self.user_id]
|
||||
if not available_invites:
|
||||
self.logger.warning("没有可用的邀请对象(排除自身后为空),跳过邀请")
|
||||
return
|
||||
random_invite = random.choice(available_invites)
|
||||
url = f'{self.BASE_URL}/commonPost/~memberNonactivity~worldCupIndexService~index'
|
||||
data = {"inviteType": 1, "inviteUserId": random_invite}
|
||||
resp = self.http.request(url, data=data)
|
||||
if resp and resp.get('success'):
|
||||
self.logger.success(f"已向邀请对象发起邀请 (userId: {random_invite})")
|
||||
else:
|
||||
err = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f"邀请失败: {err}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"邀请初始化异常: {str(e)}")
|
||||
|
||||
def get_invite_list(self) -> Optional[List[Dict]]:
|
||||
"""查询已邀请好友列表"""
|
||||
resp = self._post('/commonPost/~memberNonactivity~worldCupTaskService~taskInviteList', {})
|
||||
if resp and resp.get('success'):
|
||||
return resp.get('obj', [])
|
||||
err = resp.get('errorMessage', '未知错误') if resp else '请求失败'
|
||||
self.logger.warning(f"查询邀请列表失败: {err}")
|
||||
return None
|
||||
|
||||
def do_show_invite_list(self) -> None:
|
||||
"""查询并显示已邀请好友列表"""
|
||||
invite_list = self.get_invite_list()
|
||||
if invite_list:
|
||||
self.logger.success(f"已邀请 {len(invite_list)} 位好友:")
|
||||
for i, friend in enumerate(invite_list, 1):
|
||||
mobile = friend.get('mobile', '未知')
|
||||
invite_date = friend.get('inviteDate', '未知')
|
||||
# 脱敏显示
|
||||
if len(mobile) >= 7:
|
||||
mobile_masked = mobile[:3] + "****" + mobile[7:]
|
||||
else:
|
||||
mobile_masked = mobile
|
||||
self.logger.info(f" {i}. {mobile_masked} (邀请时间: {invite_date})")
|
||||
else:
|
||||
self.logger.info("暂无已邀请好友")
|
||||
# =================================
|
||||
|
||||
def do_daily_gift(self, result: Dict) -> None:
|
||||
self.logger.task('[每日礼物] 检查状态...')
|
||||
status = self.get_daily_gift_status()
|
||||
if status is None:
|
||||
self.logger.warning('[每日礼物] 获取状态失败')
|
||||
return
|
||||
if status.get('received'):
|
||||
self.logger.success('[每日礼物] 今日已领取')
|
||||
return
|
||||
if not status.get('canReceive'):
|
||||
self.logger.info('[每日礼物] 今日不可领取')
|
||||
return
|
||||
self.logger.task('[每日礼物] 尝试领取...')
|
||||
if self.receive_daily_gift():
|
||||
self.logger.success('[每日礼物] 领取成功')
|
||||
result['daily_gift'] = True
|
||||
else:
|
||||
self.logger.warning('[每日礼物] 领取失败')
|
||||
|
||||
def do_tasks(self, result: Dict) -> None:
|
||||
self.logger.info('正在获取世界杯活动任务列表...')
|
||||
tasks = self.get_task_list()
|
||||
if tasks is None:
|
||||
return
|
||||
self.logger.info(f'共发现 {len(tasks)} 个任务')
|
||||
|
||||
for task in tasks:
|
||||
task_name = task.get('taskName', '未知')
|
||||
task_type = task.get('taskType', '')
|
||||
task_code = task.get('taskCode', '')
|
||||
status = task.get('status')
|
||||
rest_finish = task.get('restFinishTime', 0)
|
||||
can_receive = task.get('canReceiveTokenNum', 0)
|
||||
|
||||
if task_code not in AUTO_FINISH_TASK_CODES:
|
||||
continue
|
||||
|
||||
self.logger.info(f'[{task_name}]')
|
||||
|
||||
if status == 3 or (status == 1 and rest_finish <= 0):
|
||||
self.logger.success(f'[{task_name}] 已完成')
|
||||
continue
|
||||
|
||||
if self.finish_task(task_code):
|
||||
self.logger.success(f'[{task_name}] 完成成功')
|
||||
result['tasks_completed'] += 1
|
||||
else:
|
||||
self.logger.warning(f'[{task_name}] 完成失败')
|
||||
time.sleep(1)
|
||||
|
||||
def do_fetch_rewards(self, result: Dict) -> None:
|
||||
self.logger.info('领取任务奖励...')
|
||||
reward_resp = self.fetch_task_reward()
|
||||
if reward_resp:
|
||||
received = reward_resp.get('receivedAccountList', [])
|
||||
if received:
|
||||
for item in received:
|
||||
currency = item.get('currency', '')
|
||||
amount = item.get('amount', 0)
|
||||
self.logger.success(f'领取: {currency} x{amount}')
|
||||
else:
|
||||
self.logger.info('无新奖励可领取')
|
||||
|
||||
def play_game(self, result: Dict) -> None:
|
||||
self.logger.info('⚽ 开始挑战射门游戏...')
|
||||
game_info = self.get_game_index()
|
||||
if game_info is None:
|
||||
self.logger.warning('获取游戏配置失败')
|
||||
return
|
||||
|
||||
self.level_list = game_info.get('levelList', [])
|
||||
cur_stage = game_info.get('curStage', 1)
|
||||
cur_level = game_info.get('curLevel', 1)
|
||||
|
||||
if not self.level_list:
|
||||
self.logger.warning('游戏关卡列表为空')
|
||||
return
|
||||
|
||||
self.logger.info(f'当前进度: 第{cur_stage}阶段 第{cur_level}关')
|
||||
|
||||
for level_cfg in self.level_list:
|
||||
level_no = level_cfg.get('level')
|
||||
target = level_cfg.get('target', 0)
|
||||
reward = level_cfg.get('rewardCoins', 0)
|
||||
|
||||
if level_no < cur_level:
|
||||
continue
|
||||
|
||||
self.logger.task(f'挑战第{level_no}关(目标 {target} 次进球,奖励 {reward} 金币)')
|
||||
|
||||
pass_result = self.report_pass(level_no, target)
|
||||
|
||||
attempts = 0
|
||||
while attempts < 3 and (pass_result is None or not pass_result.get('coinNum', 0) >= reward):
|
||||
time.sleep(1)
|
||||
pass_result = self.report_pass(level_no, target)
|
||||
attempts += 1
|
||||
|
||||
if pass_result:
|
||||
result['stages_passed'].append({
|
||||
'level': level_no,
|
||||
'coins': pass_result.get('coinNum', 0),
|
||||
'reward': reward,
|
||||
})
|
||||
result['game_coins'] += pass_result.get('coinNum', 0)
|
||||
self.logger.goal(f'第{level_no}关完成,获得 {pass_result.get("coinNum", 0)} 金币')
|
||||
else:
|
||||
self.logger.warning(f'第{level_no}关未通过')
|
||||
|
||||
def do_bet(self, result: Dict) -> None:
|
||||
self.logger.info('⚽ 查询比赛竞猜状态...')
|
||||
bet_status = self.get_bet_status()
|
||||
if bet_status is None:
|
||||
return
|
||||
|
||||
account = bet_status.get('currentAccount', {})
|
||||
balance = account.get('balance', 0)
|
||||
self.logger.info(f'当前金币余额: {balance}')
|
||||
|
||||
matches = bet_status.get('matchList', [])
|
||||
pending = [
|
||||
m for m in matches
|
||||
if m.get('matchStatus') == 'Fixture' and m.get('recordStatus', 0) == 0
|
||||
]
|
||||
self.logger.info(f'共 {len(matches)} 场比赛,{len(pending)} 场可下注')
|
||||
self.logger.info(f'单场下注: {BET_COIN} 金币')
|
||||
|
||||
if balance < BET_COIN:
|
||||
self.logger.warning(f'余额不足 {BET_COIN} 金币,跳过下注')
|
||||
result['final_balance'] = balance
|
||||
result['final_total'] = account.get('totalAmount', 0)
|
||||
return
|
||||
|
||||
choice_map = {0: '主胜', 1: '平', 2: '客胜'}
|
||||
|
||||
for m in pending:
|
||||
match_id = m.get('matchId')
|
||||
team_a = m.get('teamAName', '?')
|
||||
team_b = m.get('teamBName', '?')
|
||||
odds_str = m.get('odds', '?,?,?')
|
||||
bet_result = random.randint(0, 2)
|
||||
|
||||
try:
|
||||
odds_list = [float(x) for x in odds_str.split(',')]
|
||||
picked_odds = odds_list[bet_result] if bet_result < len(odds_list) else 0.0
|
||||
except (ValueError, IndexError):
|
||||
picked_odds = 0.0
|
||||
|
||||
potential_payout = round(BET_COIN * picked_odds, 2)
|
||||
potential_profit = round(potential_payout - BET_COIN, 2)
|
||||
|
||||
self.logger.task(
|
||||
f'下注: {team_a} vs {team_b}(赔率 {odds_str})→ '
|
||||
f'{choice_map[bet_result]} {BET_COIN} 金币 '
|
||||
f'| 命中得 {potential_payout}(净赚 {potential_profit})'
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
if self.place_bet(match_id, bet_result=bet_result, bet_coin=BET_COIN):
|
||||
self.logger.success(
|
||||
f'下注成功: {team_a} vs {team_b}(赔率 {picked_odds},命中得 {potential_payout})'
|
||||
)
|
||||
result['bets_placed'] += 1
|
||||
result['bet_details'].append({
|
||||
'match': f'{team_a} vs {team_b}',
|
||||
'choice': choice_map[bet_result],
|
||||
'odds': picked_odds,
|
||||
'bet_coin': BET_COIN,
|
||||
'potential_payout': potential_payout,
|
||||
'potential_profit': potential_profit,
|
||||
})
|
||||
else:
|
||||
self.logger.warning(f'下注失败: {team_a} vs {team_b}')
|
||||
|
||||
status_after = self.get_bet_status()
|
||||
if status_after:
|
||||
acc_after = status_after.get('currentAccount', {})
|
||||
result['final_balance'] = acc_after.get('balance', 0)
|
||||
result['final_total'] = acc_after.get('totalAmount', 0)
|
||||
else:
|
||||
result['final_balance'] = balance
|
||||
result['final_total'] = account.get('totalAmount', 0)
|
||||
|
||||
def do_settle(self, result: Dict) -> None:
|
||||
"""结算已开赛下注的实际盈亏"""
|
||||
self.logger.info('💰 结算昨日下注结果...')
|
||||
settled = self.settle_bet()
|
||||
if settled is None:
|
||||
self.logger.warning('结算接口失败,跳过')
|
||||
return
|
||||
|
||||
if not settled:
|
||||
self.logger.info('今日无新结算记录')
|
||||
return
|
||||
|
||||
choice_map = {0: '主胜', 1: '平', 2: '客胜'}
|
||||
|
||||
total_pnl = 0
|
||||
wins = 0
|
||||
losses = 0
|
||||
|
||||
for m in settled:
|
||||
match_id = m.get('matchId', '?')
|
||||
team_a = m.get('teamAName', '?')
|
||||
team_b = m.get('teamBName', '?')
|
||||
score = f"{m.get('teamAScore', '?')}:{m.get('teamBScore', '?')}"
|
||||
bet_coin = m.get('betCoin', 0)
|
||||
bet_result = m.get('betResult', -1)
|
||||
bet_odds_str = m.get('betOdds', '?,?,?')
|
||||
win_coin = m.get('betWinCoin', 0)
|
||||
|
||||
pnl = win_coin - bet_coin
|
||||
total_pnl += pnl
|
||||
|
||||
try:
|
||||
odds_list = [float(x) for x in bet_odds_str.split(',')]
|
||||
picked_odds = odds_list[bet_result] if 0 <= bet_result < len(odds_list) else 0.0
|
||||
except (ValueError, IndexError):
|
||||
picked_odds = 0.0
|
||||
|
||||
if pnl > 0:
|
||||
wins += 1
|
||||
icon = '🎉'
|
||||
pnl_str = f'+{pnl}'
|
||||
elif pnl < 0:
|
||||
losses += 1
|
||||
icon = '💔'
|
||||
pnl_str = f'{pnl}'
|
||||
else:
|
||||
icon = '➖'
|
||||
pnl_str = '0'
|
||||
|
||||
self.logger.info(
|
||||
f' {icon} {team_a} vs {team_b}({score})→ '
|
||||
f'押{choice_map.get(bet_result, "?")} 赔率 {picked_odds} | '
|
||||
f'投 {bet_coin} 得 {win_coin} | {pnl_str}'
|
||||
)
|
||||
|
||||
result['settle_details'].append({
|
||||
'match': f'{team_a} vs {team_b}',
|
||||
'score': score,
|
||||
'choice': choice_map.get(bet_result, '?'),
|
||||
'bet_coin': bet_coin,
|
||||
'win_coin': win_coin,
|
||||
'pnl': pnl,
|
||||
})
|
||||
|
||||
result['settle_pnl'] = total_pnl
|
||||
result['settle_wins'] = wins
|
||||
result['settle_losses'] = losses
|
||||
|
||||
if total_pnl > 0:
|
||||
self.logger.success(f'结算完成: {wins}胜{losses}负,净赚 +{total_pnl} 金币')
|
||||
elif total_pnl < 0:
|
||||
self.logger.warning(f'结算完成: {wins}胜{losses}负,净亏 {total_pnl} 金币')
|
||||
else:
|
||||
self.logger.info(f'结算完成: {wins}胜{losses}负,持平')
|
||||
|
||||
status_after = self.get_bet_status()
|
||||
if status_after:
|
||||
acc_after = status_after.get('currentAccount', {})
|
||||
result['final_balance'] = acc_after.get('balance', 0)
|
||||
result['final_total'] = acc_after.get('totalAmount', 0)
|
||||
|
||||
def show_status(self) -> None:
|
||||
self.logger.info('=' * 20 + ' 世界杯活动状态 ' + '=' * 20)
|
||||
bet_status = self.get_bet_status()
|
||||
if bet_status:
|
||||
account = bet_status.get('currentAccount', {})
|
||||
self.logger.info(f'金币余额: {account.get("balance", 0)} / 累计: {account.get("totalAmount", 0)}')
|
||||
|
||||
daily = self.get_daily_gift_status()
|
||||
if daily:
|
||||
status_str = '已领取' if daily.get('received') else ('可领取' if daily.get('canReceive') else '不可领取')
|
||||
self.logger.info(f'每日礼物: {status_str}')
|
||||
|
||||
game = self.get_game_index()
|
||||
if game:
|
||||
self.logger.info(f'游戏阶段: 第{game.get("curStage", 1)}阶段 第{game.get("curLevel", 1)}关')
|
||||
|
||||
index = self.get_activity_index()
|
||||
if index:
|
||||
self.logger.info(f'活动周期: {index.get("acStartTime", "")} ~ {index.get("acEndTime", "")}')
|
||||
self.logger.info(f'可领金币: {index.get("availableRewardCoins", 0)}')
|
||||
|
||||
self.logger.info('=' * 56)
|
||||
|
||||
def run(self) -> Dict[str, Any]:
|
||||
result = {
|
||||
'tasks_completed': 0,
|
||||
'game_coins': 0,
|
||||
'stages_passed': [],
|
||||
'daily_gift': False,
|
||||
'bets_placed': 0,
|
||||
'bet_details': [],
|
||||
'settle_pnl': 0,
|
||||
'settle_wins': 0,
|
||||
'settle_losses': 0,
|
||||
'settle_details': [],
|
||||
'final_balance': 0,
|
||||
'final_total': 0,
|
||||
}
|
||||
|
||||
# 1. 邀请好友(先发起邀请)
|
||||
self.do_invite()
|
||||
|
||||
# 2. 查询并显示已邀请好友列表
|
||||
self.do_show_invite_list()
|
||||
|
||||
# 3. 获取活动首页信息
|
||||
index_info = self.get_activity_index()
|
||||
if index_info:
|
||||
self.logger.success(f'已加入世界杯活动,可领 {index_info.get("availableRewardCoins", 0)} 金币')
|
||||
|
||||
if ENABLE_DAILY_GIFT:
|
||||
self.do_daily_gift(result)
|
||||
if ENABLE_TASKS:
|
||||
self.do_tasks(result)
|
||||
self.do_fetch_rewards(result)
|
||||
if ENABLE_GAME:
|
||||
self.play_game(result)
|
||||
if ENABLE_BET:
|
||||
self.do_bet(result)
|
||||
else:
|
||||
self.logger.info('⚽ [开关] 比赛下注已关闭,跳过')
|
||||
if ENABLE_SETTLE:
|
||||
self.do_settle(result)
|
||||
else:
|
||||
self.logger.info('💰 [开关] 下注结算已关闭,跳过')
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def run_account(account_url: str, index: int, invite_pool: List[str]) -> Dict[str, Any]:
|
||||
logger = Logger()
|
||||
proxy_url = os.getenv('SF_PROXY_API_URL', '')
|
||||
proxy_manager = ProxyManager(proxy_url)
|
||||
|
||||
http = SFHttpClient(proxy_manager)
|
||||
retry_count = 0
|
||||
login_success = False
|
||||
phone = ''
|
||||
user_id = ''
|
||||
|
||||
while retry_count < MAX_PROXY_RETRIES and not login_success:
|
||||
try:
|
||||
if retry_count > 0:
|
||||
http = SFHttpClient(proxy_manager)
|
||||
success, user_id, phone = http.login(account_url)
|
||||
if success:
|
||||
login_success = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
retry_count += 1
|
||||
if retry_count < MAX_PROXY_RETRIES:
|
||||
time.sleep(2)
|
||||
|
||||
if not login_success:
|
||||
logger.error(f'账号{index + 1} 登录失败')
|
||||
return {
|
||||
'success': False, 'phone': '', 'index': index,
|
||||
'tasks_completed': 0, 'game_coins': 0, 'stages_passed': [],
|
||||
'daily_gift': False, 'bets_placed': 0, 'bet_details': [],
|
||||
'settle_pnl': 0, 'settle_wins': 0, 'settle_losses': 0, 'settle_details': [],
|
||||
'final_balance': 0, 'final_total': 0,
|
||||
}
|
||||
|
||||
masked_phone = phone[:3] + "****" + phone[7:] if len(phone) >= 7 else phone
|
||||
logger.success(f'账号{index + 1}: 【{masked_phone}】登录成功')
|
||||
|
||||
time.sleep(random.uniform(1, 3))
|
||||
|
||||
executor = WorldCupExecutor(http, logger, user_id, invite_pool)
|
||||
activity_result = executor.run()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'phone': phone,
|
||||
'index': index,
|
||||
**activity_result,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
# 收集所有 sfsyUrl 相关环境变量(兼容青龙面板同名多变量)
|
||||
account_urls = []
|
||||
invite_pool = [] #
|
||||
Reference in New Issue
Block a user