Files
hlt1995-qlScripts/Backup/#SFExpress_NewYear.py
T

1083 lines
43 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: 0 12 * * *
# const $ = new Env('顺丰速运新年活动')
Author: 爱学习的呆子
Version: 2.0.0
Date: 2026-01-26
活动代码: YEAREND_2025
配置说明:
- ENABLE_INTEGRAL_EXCHANGE: 积分兑换开关,True=启用积分兑换冲刺次数,False=禁用 #脚本55行 默认为False
- ENABLE_LOTTERY_DRAW: 轮次抽奖开关,True=启用向前冲游戏后自动轮次抽奖,False=禁用 #脚本56行 默认为False
- SF_PROXY_API_URL: 代理API地址,为空则不使用代理 #脚本57行
推荐微信扫码网站:
- sm.linzixuan.work
"""
import hashlib
import json
import os
import random
import time
from datetime import datetime
from typing import Dict, Optional, Any, List
from dataclasses import dataclass
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)
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
print(f'⚠️ 并发数量超过最大值20,已自动调整为20')
elif CONCURRENT_NUM < 1:
CONCURRENT_NUM = 1
print(f'⚠️ 并发数量小于1,已自动调整为1(串行模式)')
print_lock = Lock()
@dataclass
class Config:
"""全局配置"""
APP_NAME: str = "顺丰速运新年活动"
VERSION: str = "1.0.2"
ENV_NAME: str = "sfsyUrl"
PROXY_API_URL: str = os.getenv('SF_PROXY_API_URL', '')
ACTIVITY_CODE: str = "YEAREND_2025"
TOKEN: str = 'wwesldfs29aniversaryvdld29'
SYS_CODE: str = 'MCS-MIMP-CORE'
ENABLE_INTEGRAL_EXCHANGE: bool = False #积分兑换开关
ENABLE_LOTTERY_DRAW: bool = False #轮次抽奖开关
class Logger:
"""日志管理器"""
ICONS = {
'info': '📝',
'success': '✨',
'error': '❌',
'warning': '⚠️',
'user': '👤',
'money': '💰',
'gift': '🎁',
}
def __init__(self):
self.messages: List[str] = []
self.current_account_msg: List[str] = []
self.lock = Lock()
def _format_msg(self, icon: str, content: str) -> str:
return f"{icon} {content}"
def _safe_print(self, msg: str):
with print_lock:
print(msg)
def info(self, content: str):
msg = self._format_msg(self.ICONS['info'], content)
self._safe_print(msg)
with self.lock:
self.current_account_msg.append(msg)
self.messages.append(msg)
def success(self, content: str):
msg = self._format_msg(self.ICONS['success'], content)
self._safe_print(msg)
with self.lock:
self.current_account_msg.append(msg)
self.messages.append(msg)
def error(self, content: str):
msg = self._format_msg(self.ICONS['error'], content)
self._safe_print(msg)
with self.lock:
self.current_account_msg.append(msg)
self.messages.append(msg)
def warning(self, content: str):
msg = self._format_msg(self.ICONS['warning'], content)
self._safe_print(msg)
with self.lock:
self.current_account_msg.append(msg)
self.messages.append(msg)
def user_info(self, account_index: int, mobile: str):
msg = self._format_msg(self.ICONS['user'], f"账号{account_index}: 【{mobile}】登录成功")
self._safe_print(msg)
with self.lock:
self.current_account_msg.append(msg)
self.messages.append(msg)
def reset_account_msg(self):
self.current_account_msg = []
def get_all_messages(self) -> str:
return '\n'.join(self.messages)
def get_account_messages(self) -> str:
return '\n'.join(self.current_account_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:
print('⚠️ 未配置代理API地址,将不使用代理')
return None
response = requests.get(self.api_url, timeout=10)
if response.status_code == 200:
proxy_text = response.text.strip()
if ':' in proxy_text:
if proxy_text.startswith('http://') or proxy_text.startswith('https://'):
proxy = proxy_text
else:
proxy = f'http://{proxy_text}'
display_proxy = proxy
if '@' in proxy:
parts = proxy.split('@')
if len(parts) == 2:
display_proxy = f"http://***:***@{parts[1]}"
print(f"✅ 成功获取代理: {display_proxy}")
return {'http': proxy, 'https': proxy}
print(f'❌ 获取代理失败: {response.text}')
return None
except Exception as e:
print(f'❌ 获取代理异常: {str(e)}')
return None
class SFHttpClient:
"""顺丰HTTP客户端"""
def __init__(self, config: Config, proxy_manager: ProxyManager):
self.config = config
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
self.headers = {
'Host': 'mcs-mimp-web.sf-express.com',
'User-Agent': 'Mozilla/5.0 (Linux; Android 16; PJE110 Build/TP1A.220905.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.135 Mobile Safari/537.36 mediaCode=SFEXPRESSAPP-Android-ML',
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Content-Type': 'application/json',
'sec-ch-ua-platform': '"Android"',
'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Android WebView";v="134"',
'sec-ch-ua-mobile': '?1',
'channel': 'daluapp',
'syscode': 'MCS-MIMP-CORE',
'platform': 'SFAPP',
'origin': 'https://mcs-mimp-web.sf-express.com',
'x-requested-with': 'com.sf.activity',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'priority': 'u=1, i'
}
def _generate_sign(self) -> Dict[str, str]:
timestamp = str(int(round(time.time() * 1000)))
data = f'token={self.config.TOKEN}&timestamp={timestamp}&sysCode={self.config.SYS_CODE}'
signature = hashlib.md5(data.encode()).hexdigest()
return {
'timestamp': timestamp,
'signature': signature
}
def request(
self,
url: str,
method: str = 'POST',
data: Optional[Dict] = None,
max_retries: int = REQUEST_RETRY_COUNT
) -> Optional[Dict[str, Any]]:
sign_data = self._generate_sign()
self.headers.update(sign_data)
retry_count = 0
proxy_retry_count = 0
while proxy_retry_count < MAX_PROXY_RETRIES:
try:
if retry_count >= 2:
print('请求已失败2次,尝试切换代理IP')
new_proxy = self.proxy_manager.get_proxy()
if new_proxy:
self.session.proxies = new_proxy
else:
print('⚠️ 切换代理失败,无可用代理')
retry_count = 0
try:
if method.upper() == 'GET':
response = self.session.get(url, headers=self.headers, timeout=PROXY_TIMEOUT)
elif method.upper() == 'POST':
response = self.session.post(url, headers=self.headers, json=data or {}, timeout=PROXY_TIMEOUT)
else:
raise ValueError(f'不支持的请求方法: {method}')
response.raise_for_status()
try:
res = response.json()
if res is None:
print(f'响应内容为空,正在重试 ({retry_count + 1}/{max_retries})')
retry_count += 1
time.sleep(2)
continue
return res
except (json.JSONDecodeError, ValueError) as e:
print(f'JSON解析失败: {str(e)}, 响应内容: {response.text[:200]}')
retry_count += 1
if retry_count < max_retries:
print(f'正在进行第{retry_count + 1}次重试...')
time.sleep(2)
continue
return None
except requests.exceptions.RequestException as e:
retry_count += 1
print(f'请求失败,正在重试 ({retry_count}/{max_retries}): {str(e)}')
if 'ProxyError' in str(e) or 'SSLError' in str(e):
proxy_retry_count += 1
print(f'代理连接失败,尝试切换代理 ({proxy_retry_count}/{MAX_PROXY_RETRIES})')
if proxy_retry_count < MAX_PROXY_RETRIES:
new_proxy = self.proxy_manager.get_proxy()
if new_proxy:
self.session.proxies = new_proxy
time.sleep(2)
continue
except Exception as e:
print(f'请求发生异常: {str(e)}')
proxy_retry_count += 1
if proxy_retry_count < MAX_PROXY_RETRIES:
print(f'尝试切换代理 ({proxy_retry_count}/{MAX_PROXY_RETRIES})')
time.sleep(2)
continue
else:
print('达到最大代理重试次数,返回None')
return None
print('请求最终失败,返回None')
return None
def login(self, url: str, timeout: int = PROXY_TIMEOUT) -> tuple[bool, str, str]:
try:
decoded_url = unquote(url) #适合编码后的url跑 也就是插件提交上来 用&分割的
#decoded_url = url #适合未编码的url跑 也就是正常的URL 手动抓的那种 用\n换行分割 需修改1002行的&分割为\n
self.session.get(decoded_url, headers=self.headers, timeout=timeout)
cookies = self.session.cookies.get_dict()
user_id = cookies.get('_login_user_id_', '')
phone = cookies.get('_login_mobile_', '')
if phone:
return True, user_id, phone
else:
return False, '', ''
except Exception as e:
print(f'登录异常: {str(e)}')
return False, '', ''
class NewYearActivity:
"""新年活动任务执行器"""
def __init__(
self,
http_client: SFHttpClient,
logger: Logger,
config: Config,
user_id: str
):
self.http = http_client
self.logger = logger
self.config = config
self.user_id = user_id
def init_activity_index(self) -> bool:
"""初始化活动索引(必须在所有操作前调用)"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonNoLoginPost/~memberNonactivity~yearEnd2025IndexService~index'
# 使用随机邀请ID(如果没有则使用空字符串)
invite_ids = [
'F5E70D771ABD4D86AFB0782313945C91',
'16A1E9AF32C1441894D27E0F1453F7CB',
'3ED135C9A3254CCFACA88781CD9B3A91',
'6931380B6A234074A318CECD9E62089D',
'C883BE3AE638494B90BAB440A4CFFDEC'
]
# 随机选择一个邀请ID(排除自己的user_id)
available_invites = [inv for inv in invite_ids if inv != self.user_id]
invite_user_id = random.choice(available_invites) if available_invites else invite_ids[0]
data = {
"inviteType": 1,
"inviteUserId": invite_user_id
}
response = self.http.request(url, data=data)
if response and response.get('success'):
return True
else:
self.logger.warning('活动索引初始化失败')
return False
def request_activity_page(self) -> bool:
"""请求活动页面(避免活动太火爆错误)"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonNoLoginPost/~cempBase~pageGreyStrategyService~getStrategyByUser'
data = {"sceneCode": "year-end-2025"}
response = self.http.request(url, data=data)
if response and response.get('success'):
return True
else:
self.logger.warning('活动页面请求失败')
return False
def check_sign_status(self) -> tuple[bool, Dict]:
"""检查签到状态"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activitySignService~signStatus'
data = {"activityCode": self.config.ACTIVITY_CODE}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
sign_count_cache = obj.get('signCountCache', {})
sign_count = sign_count_cache.get('signCount', 0)
sign_time = sign_count_cache.get('signTime', '')
sign_expired_tm = sign_count_cache.get('signExpiredTm', '')
self.logger.info(f'累计签到次数: {sign_count}')
if sign_time:
self.logger.info(f'最后签到时间: {sign_time}')
if sign_expired_tm:
self.logger.info(f'活动截止时间: {sign_expired_tm}')
return True, obj
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.error(f'查询签到状态失败: {error_msg}')
return False, {}
def sign_in(self) -> tuple[bool, str]:
"""执行签到"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activitySignService~sign'
data = {
"activityCode": self.config.ACTIVITY_CODE
}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
signed = obj.get('signed', False)
sign_count = obj.get('signCount', 0)
if signed:
self.logger.warning('今日已签到')
return True, '今日已签到'
common_sign_packet = obj.get('commonSignPacketDTO', {})
if common_sign_packet:
gift_bag_name = common_sign_packet.get('giftBagName', '未知奖励')
gift_bag_worth = common_sign_packet.get('giftBagWorth', 0)
product_list = common_sign_packet.get('commonSignProductList', [])
reward_details = []
for product in product_list:
product_name = product.get('productName', '')
amount = product.get('amount', 0)
if product_name and amount:
reward_details.append(f'{product_name} x{amount}')
reward_text = ', '.join(reward_details) if reward_details else gift_bag_name
self.logger.success(f'签到成功!获得: {reward_text}')
self.logger.info(f'累计签到: {sign_count} 次')
return True, ''
else:
self.logger.success(f'签到成功!累计签到: {sign_count} 次')
return True, ''
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.error(f'签到失败: {error_msg}')
return False, error_msg
def get_task_list(self) -> List[Dict]:
"""获取任务列表"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activityTaskService~taskList'
data = {
"activityCode": self.config.ACTIVITY_CODE,
"channelType": "SFAPP"
}
response = self.http.request(url, data=data)
if response and response.get('success'):
task_list = response.get('obj', [])
self.logger.info(f'成功获取任务列表,共 {len(task_list)} 个任务')
return task_list
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.error(f'获取任务列表失败: {error_msg}')
return []
def receive_task_reward(self, task_code: str, task_name: str) -> bool:
"""领取任务奖励"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonRoutePost/memberEs/taskRecord/finishTask'
data = {
"taskCode": task_code
}
response = self.http.request(url, data=data)
if response and response.get('success'):
self.logger.success(f'[{task_name}] 奖励领取成功')
return True
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'[{task_name}] 领取奖励失败: {error_msg}')
return False
def receive_member_equity(self) -> bool:
"""领取寄件会员权益"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberManage~memberEquity~commonEquityReceive'
data = {"key": "surprise_benefit"}
response = self.http.request(url, data=data)
if response and response.get('success'):
self.logger.success('领取寄件会员权益成功')
return True
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'领取寄件会员权益失败: {error_msg}')
return False
def do_tasks(self) -> int:
"""执行所有任务"""
task_list = self.get_task_list()
if not task_list:
return 0
completed_count = 0
# 需要跳过的任务(无需显示日志)
skip_task_names = [
'开通家庭8折互寄权益',
'去寄快递',
'充值新速运通全国卡',
'邀好友首次访问活动',
'春节寄大件行李'
]
for task in task_list:
task_name = task.get('taskName', '未知任务')
task_code = task.get('taskCode', '')
task_type = task.get('taskType', '')
status = task.get('status', 0)
process = task.get('process', '0/0')
description = task.get('description', '')
virtual_token_num = task.get('virtualTokenNum', 0)
if status == 3:
self.logger.success(f'[{task_name}] 已完成 ({process})')
completed_count += 1
elif status == 2:
# 跳过无taskCode且在跳过列表中的任务
if not task_code and task_name in skip_task_names:
continue
self.logger.info(f'[{task_name}] 可领取 - {description} (奖励: {virtual_token_num}次)')
if task_name == '领取寄件会员权益':
if self.receive_member_equity():
completed_count += 1
time.sleep(1)
elif task_name == '积分兑冲刺次数':
self.logger.info('积分兑换任务将在游戏前自动执行')
elif task_name == '套财神游戏':
self.logger.info('套财神游戏任务将在后续执行')
elif task_code:
if self.receive_task_reward(task_code, task_name):
completed_count += 1
time.sleep(1)
else:
self.logger.info(f'[{task_name}] 未完成 ({process}) - {description}')
time.sleep(0.5)
self.logger.success(f'任务执行完成,共完成 {completed_count} 个任务')
return completed_count
def get_forward_status(self) -> tuple[bool, Dict]:
"""查询向前冲状态"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025ForwardService~forwardStatus'
data = {}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
return True, obj
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.error(f'查询向前冲状态失败: {error_msg}')
return False, {}
def play_forward_game(self, card_token: str = None) -> tuple[bool, str]:
"""执行向前冲游戏
Args:
card_token: 卡片令牌,如果为None则获取新令牌
Returns:
tuple[bool, str]: (是否成功, cardToken)
"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025ForwardService~forward'
if card_token:
data = {"cardToken": card_token}
else:
data = {}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
new_card_token = obj.get('cardToken', '')
current_times = obj.get('currentTimes', 0)
total_times = obj.get('totalTimes', 0)
current_ratio = obj.get('currentRatio', 0)
remain_chance = obj.get('remainChance', 0)
result_type = obj.get('resultType', 0)
if result_type == 5:
self.logger.info(f'🎮 向前冲游戏进行中... 进度: {current_times}/{total_times} ({current_ratio:.1f}%) 剩余次数: {remain_chance}')
else:
self.logger.success(f'🎮 向前冲游戏 - 进度: {current_times}/{total_times} ({current_ratio:.1f}%) 剩余次数: {remain_chance}')
return True, new_card_token
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'向前冲游戏失败: {error_msg}')
return False, ''
def play_game(self) -> bool:
"""执行套财神游戏"""
self.logger.info('开始执行套财神游戏...')
url_init = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025GameService~init'
response = self.http.request(url_init, data={})
if not response or not response.get('success'):
self.logger.warning('游戏初始化失败')
return False
obj = response.get('obj', {})
if obj.get('alreadyDayPass', False):
self.logger.info('今日已通关,跳过游戏')
return True
start_level = obj.get('currentIndex', 0)
level_config = obj.get('levelConfig', [])
total_levels = len(level_config)
self.logger.info(f'今日未通关,从第 {start_level} 关开始,共 {total_levels} 关')
url_win = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025GameService~win'
for i in range(start_level, total_levels+1):
self.logger.info(f'正在闯关第 {i} 关...')
data = {"levelIndex": i}
response = self.http.request(url_win, data=data)
if response and response.get('success'):
award_list = response.get('obj', {}).get('currentAwardList', [])
if award_list:
for award in award_list:
currency = award.get('currency', '')
amount = award.get('amount', 0)
self.logger.success(f'第 {i} 关通关成功!获得: {currency} x{amount}')
else:
self.logger.success(f'第 {i} 关通关成功!')
time.sleep(random.randint(5, 10))
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.error(f'第 {i} 关闯关失败: {error_msg}')
return False
self.logger.success('所有关卡通关完成!')
return True
def fetch_tasks_reward(self) -> int:
"""领取任务奖励次数"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025TaskService~fetchTasksReward'
data = {
"activityCode": self.config.ACTIVITY_CODE,
"channelType": "MINI_PROGRAM"
}
response = self.http.request(url, data=data)
if response and response.get('success'):
success, status_obj = self.get_forward_status()
if success:
remain_chance = status_obj.get('remainChance', 0)
current_ratio = status_obj.get('currentRatio', 0)
current_level = status_obj.get('currentLevel', '')
self.logger.info(f'任务奖励领取成功,当前剩余次数: {remain_chance},进度: {current_ratio:.1f}%,等级: {current_level}')
return remain_chance
return 0
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'领取任务奖励失败: {error_msg}')
return 0
def get_accrued_task_award(self) -> bool:
"""获取累计任务奖励"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025TaskService~getAccruedTaskAward'
data = {}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
current_progress = obj.get('currentProgress', 0)
progress_config = obj.get('progressConfig', {})
accrued_award = obj.get('accruedAward', {})
self.logger.info(f'累计任务进度: {current_progress}')
if accrued_award:
self.logger.success('获得累计任务奖励')
return True
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'获取累计任务奖励失败: {error_msg}')
return False
def get_user_rest_integral(self) -> int:
"""查询用户剩余积分"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~activityTaskService~getUserRestIntegral'
data = {}
response = self.http.request(url, data=data)
if response and response.get('success'):
integral = response.get('obj', 0)
self.logger.info(f'当前可用积分: {integral}')
return integral
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'查询用户剩余积分失败: {error_msg}')
return 0
def integral_exchange(self, exchange_num: int = 1) -> bool:
"""积分兑换冲刺次数
Args:
exchange_num: 兑换次数,默认1次(消耗10积分)
Returns:
bool: 是否兑换成功
"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025TaskService~integralExchange'
data = {
"exchangeNum": exchange_num,
"activityCode": self.config.ACTIVITY_CODE
}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
remain_chance = obj.get('remainChance', 0)
self.logger.success(f'💎 积分兑换成功!兑换 {exchange_num} 次冲刺机会,当前剩余: {remain_chance} 次')
return True
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'积分兑换失败: {error_msg}')
return False
def check_lottery_status(self) -> tuple[bool, str, int]:
"""检查轮次抽奖状态
Returns:
tuple[bool, str, int]: (是否可以抽奖, 当前轮次, 剩余抽奖次数)
"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025ForwardService~forwardStatus'
data = {}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
current_level = obj.get('currentLevel', '')
level_list = obj.get('levelList', [])
for level in level_list:
currency = level.get('currency', '')
if currency == current_level:
balance = level.get('balance', 0)
total_amount = level.get('totalAmount', 0)
if balance > 0:
self.logger.info(f'🎰 当前轮次: {current_level}, 可抽奖次数: {balance}/{total_amount}')
return True, current_level, balance
else:
self.logger.info(f'🎰 当前轮次: {current_level}, 本轮次抽奖次数已用完')
return False, current_level, 0
self.logger.info(f'🎰 当前轮次: {current_level}, 未找到对应轮次信息')
return False, current_level, 0
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'查询轮次抽奖状态失败: {error_msg}')
return False, '', 0
def do_lottery_draw(self, currency: str) -> bool:
"""执行轮次抽奖
Args:
currency: 轮次标识(如 LU, FU, HAPPY, LUCKY, RAISE)
Returns:
bool: 是否抽奖成功
"""
url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~yearEnd2025LotteryService~prizeDraw'
data = {"currency": currency}
response = self.http.request(url, data=data)
if response and response.get('success'):
obj = response.get('obj', {})
gift_bag_name = obj.get('giftBagName', '未知奖励')
gift_bag_worth = obj.get('giftBagWorth', 0)
product_list = obj.get('productDTOList', [])
reward_details = []
for product in product_list:
product_name = product.get('productName', '')
amount = product.get('amount', 0)
if product_name:
reward_details.append(f'{product_name} x{amount}')
reward_text = ', '.join(reward_details) if reward_details else f'{gift_bag_name} (价值{gift_bag_worth}元)'
self.logger.success(f'🎁 轮次抽奖成功!获得: {reward_text}')
return True
else:
error_msg = response.get('errorMessage', '未知错误') if response else '请求失败'
self.logger.warning(f'轮次抽奖失败: {error_msg}')
return False
def do_forward_game(self) -> int:
"""执行所有向前冲游戏次数"""
self.logger.info('开始执行向前冲游戏...')
if self.config.ENABLE_INTEGRAL_EXCHANGE:
self.logger.info('积分兑换功能已启用,尝试兑换冲刺次数...')
self.integral_exchange(1)
time.sleep(1)
success, status_obj = self.get_forward_status()
if not success:
return 0
remain_chance = status_obj.get('remainChance', 0)
if remain_chance <= 0:
self.logger.info('向前冲游戏次数已用完')
return 0
self.logger.info(f'当前剩余游戏次数: {remain_chance}')
played_count = 0
card_token = None
for i in range(remain_chance):
success, card_token = self.play_forward_game(card_token)
if success:
played_count += 1
time.sleep(1)
else:
break
if played_count > 0:
self.logger.success(f'向前冲游戏完成,共进行 {played_count} 次')
if self.config.ENABLE_LOTTERY_DRAW:
time.sleep(1)
can_lottery, current_level, lottery_balance = self.check_lottery_status()
if can_lottery and lottery_balance > 0:
self.logger.info(f'检测到可进行轮次抽奖,开始抽奖...')
for i in range(lottery_balance):
if self.do_lottery_draw(current_level):
time.sleep(1)
else:
break
return played_count
def run(self) -> Dict[str, Any]:
"""执行新年活动任务"""
print('-' * 50)
self.init_activity_index()
time.sleep(1)
self.request_activity_page()
time.sleep(1)
success, status_obj = self.check_sign_status()
if not success:
return {
'success': False,
'signed': False,
'sign_count': 0,
'task_completed': 0,
'game_played': 0
}
time.sleep(1)
sign_success, error_msg = self.sign_in()
sign_count = status_obj.get('signCountCache', {}).get('signCount', 0)
time.sleep(1)
self.fetch_tasks_reward()
time.sleep(1)
task_completed = self.do_tasks()
time.sleep(1)
self.play_game()
time.sleep(1)
self.fetch_tasks_reward()
time.sleep(1)
self.get_accrued_task_award()
time.sleep(1)
current_integral = self.get_user_rest_integral()
if current_integral >= 10 and self.config.ENABLE_INTEGRAL_EXCHANGE:
time.sleep(1)
game_played = self.do_forward_game()
return {
'success': sign_success,
'signed': '今日已签到' in error_msg,
'sign_count': sign_count,
'task_completed': task_completed,
'game_played': game_played
}
class AccountManager:
"""账号管理器"""
def __init__(self, account_url: str, account_index: int, config: Config):
self.account_url = account_url
self.account_index = account_index + 1
self.config = config
self.logger = Logger()
self.proxy_manager = ProxyManager(config.PROXY_API_URL)
self.login_success = False
self.user_id = None
self.phone = None
self.http_client = None
retry_count = 0
while retry_count < MAX_PROXY_RETRIES and not self.login_success:
try:
self.http_client = SFHttpClient(config, self.proxy_manager)
success, self.user_id, self.phone = self.http_client.login(account_url)
if success:
masked_phone = self.phone[:3] + "*" * 4 + self.phone[7:]
self.logger.user_info(self.account_index, masked_phone)
self.login_success = True
break
else:
if retry_count < MAX_PROXY_RETRIES - 1:
print(f'账号{self.account_index} 登录失败,尝试重新获取代理 ({retry_count + 1}/{MAX_PROXY_RETRIES})')
time.sleep(2)
except Exception as e:
print(f'账号{self.account_index} 登录异常: {str(e)[:100]}')
retry_count += 1
if not self.login_success:
self.logger.error(f'账号{self.account_index} 登录失败,已重试{MAX_PROXY_RETRIES}次,所有代理均不可用')
def run(self) -> Dict[str, Any]:
if not self.login_success:
return {
'success': False,
'phone': '',
'signed': False,
'sign_count': 0,
'task_completed': 0,
'game_played': 0
}
wait_time = random.randint(1000, 3000) / 1000.0
time.sleep(wait_time)
activity = NewYearActivity(self.http_client, self.logger, self.config, self.user_id)
result = activity.run()
result['phone'] = self.phone
return result
def run_single_account(account_info: str, index: int, config: Config) -> Dict[str, Any]:
try:
with print_lock:
print(f"🚀 开始执行账号{index + 1}")
account = AccountManager(account_info, index, config)
result = account.run()
if result['success']:
with print_lock:
print(f"✅ 账号{index + 1}执行完成")
else:
with print_lock:
print(f"❌ 账号{index + 1}执行失败")
result['index'] = index
return result
except Exception as e:
error_msg = f"账号{index + 1}执行异常: {str(e)}"
with print_lock:
print(f"❌ {error_msg}")
return {
'index': index,
'success': False,
'phone': '',
'signed': False,
'sign_count': 0,
'task_completed': 0,
'game_played': 0,
'error': error_msg
}
def main():
config = Config()
env_value = os.getenv(config.ENV_NAME)
if not env_value:
print(f"❌ 未找到环境变量 {config.ENV_NAME},请检查配置")
return
account_urls = [url.strip() for url in env_value.split('&') if url.strip()]
if not account_urls:
print(f"❌ 环境变量 {config.ENV_NAME} 为空或格式错误")
return
random.shuffle(account_urls)
print(f"🔀 已随机打乱账号执行顺序")
print("=" * 50)
print(f"🎉 {config.APP_NAME} v{config.VERSION}")
print(f"👨‍💻 作者: 爱学习的呆子")
print(f"🎊 活动代码: {config.ACTIVITY_CODE}")
print(f"📱 共获取到 {len(account_urls)} 个账号")
print(f"⚙️ 并发数量: {CONCURRENT_NUM}")
print(f"💎 积分兑换: {'✅ 已启用' if config.ENABLE_INTEGRAL_EXCHANGE else '❌ 已禁用'}")
print(f"🎰 轮次抽奖: {'✅ 已启用' if config.ENABLE_LOTTERY_DRAW else '❌ 已禁用'}")
print(f"⏰ 执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 50)
all_results = []
if CONCURRENT_NUM <= 1:
print("🔄 使用串行模式执行...")
for index, account_url in enumerate(account_urls):
account = AccountManager(account_url, index, config)
result = account.run()
result['index'] = index
all_results.append(result)
if index < len(account_urls) - 1:
print("=" * 50)
print(f"⏳ 等待 2 秒后执行下一个账号...")
time.sleep(2)
else:
print(f"🔄 使用并发模式执行,并发数: {CONCURRENT_NUM}")
with ThreadPoolExecutor(max_workers=CONCURRENT_NUM) as executor:
future_to_index = {
executor.submit(run_single_account, account_url, index, config): index
for index, account_url in enumerate(account_urls)
}
for future in as_completed(future_to_index):
result = future.result()
all_results.append(result)
all_results.sort(key=lambda x: x['index'])
success_count = sum(1 for r in all_results if r['success'])
fail_count = len(all_results) - success_count
signed_count = sum(1 for r in all_results if r.get('signed', False))
total_tasks = sum(r.get('task_completed', 0) for r in all_results)
total_games = sum(r.get('game_played', 0) for r in all_results)
print(f"\n" + "=" * 100)
print(f"📊 新年活动任务统计")
print("=" * 100)
print(f"{'序号':<6} {'手机号':<15} {'签到状态':<12} {'累计签到':<10} {'完成任务':<10} {'游戏次数':<10} {'状态':<10}")
print("-" * 100)
for result in all_results:
index = result['index'] + 1
phone = result['phone'][:3] + "****" + result['phone'][7:] if result['phone'] else "未登录"
signed_status = "✅已签到" if result.get('signed', False) else ("🎁新签到" if result['success'] else "❌失败")
sign_count = result.get('sign_count', 0)
task_completed = result.get('task_completed', 0)
game_played = result.get('game_played', 0)
status = "✅成功" if result['success'] else "❌失败"
print(f"{index:<6} {phone:<15} {signed_status:<12} {sign_count:<10} {task_completed:<10} {game_played:<10} {status:<10}")
print("-" * 100)
print(f"{'汇总':<6} {'总数: ' + str(len(all_results)):<15} {'已签: ' + str(signed_count):<12} {'':<10} {'任务: ' + str(total_tasks):<10} {'游戏: ' + str(total_games):<10} {'成功: ' + str(success_count):<10}")
print("=" * 100)
print("\n🎊 所有账号新年活动任务执行完成!")
if __name__ == '__main__':
main()