Files
Yangmao_Script/Points_Based/D_SFSY_SJBHD.py
T

816 lines
31 KiB
Python
Raw 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.
# 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}&timestamp={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 = [] #