From f9b59ed8a06a59d06424326bd0e27e6189742232 Mon Sep 17 00:00:00 2001 From: admin <362324317@qq.com> Date: Thu, 4 Jun 2026 22:32:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Points=5FBased/D=5FSFSY=5F?= =?UTF-8?q?DWHD.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Points_Based/D_SFSY_DWHD.py | 759 ++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 Points_Based/D_SFSY_DWHD.py diff --git a/Points_Based/D_SFSY_DWHD.py b/Points_Based/D_SFSY_DWHD.py new file mode 100644 index 0000000..b2076fb --- /dev/null +++ b/Points_Based/D_SFSY_DWHD.py @@ -0,0 +1,759 @@ +# cron: 19 12,20 * * * +# const $ = new Env("顺丰2026端午活动"); +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 = [] # 运行时从账号列表动态提取 + + +# ==================== 配置常量 ==================== +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', 'flase').lower() == 'true' + +print_lock = Lock() + +# 端午龙舟活动配置 +ACTIVITY_CODE = "DRAGONBOAT_2026" +TOKEN = 'wwesldfs29aniversaryvdld29' +SYS_CODE = 'MCS-MIMP-CORE' + +# 需要跳过的任务类型(需要实际操作的) +SKIP_TASK_TYPES = [ + 'SEND_INTERNATIONAL_PACKAGE', # 寄一单国际件 + 'LOOK_BIG_PACKAGE_GET_CASH', # 寄大件重货 + 'SEND_SUCCESS_RECALL', # 去寄快递 + 'CHARGE_NEW_EXPRESS_CARD', # 充值新速运通全国卡 + 'CHARGE_COLLECT_ALL', # 充值得8888金币 + 'OPEN_FAMILY_HOME_MUTUAL', # 开通家庭8折互寄权益 + 'BUY_PAID_PACKET', # 购买付费券包 + 'INVITEFRIENDS_PARTAKE_ACTIVITY', # 邀好友首次访问活动 +] + + +# ==================== 日志 ==================== +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]]: + # 如果代理开关关闭,直接返回 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() + # 假设 JSON 结构为 {"data":{"list":[{"ip":"x.x.x.x","port":"xxxx"}]}} + 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 + else: + 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 + + # 根据开关决定是否尝试获取代理 + if ENABLE_PROXY: + proxy = self.proxy_manager.get_proxy() + if proxy: + self.session.proxies = proxy + else: + if self.proxy_manager.api_url: + print("⚠️ 代理获取失败,将不使用代理") + # else: + # print("🔒 代理已禁用(ENABLE_PROXY=false)") + + 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 + # 如果代理开关关闭,最多只进行一次尝试(不切换代理) + 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: + """登录(兼容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 DragonBoatExecutor: + # MODIFIED: 增加 user_id 参数 + 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 [] + + # ---------- API 方法 ---------- + + def get_activity_index(self) -> Optional[Dict]: + """获取端午龙舟活动首页信息""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026IndexService~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": "SFAPP" + } + 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 get_accrued_task_award(self) -> Optional[Dict]: + """获取累计任务奖励进度""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026TaskService~getAccruedTaskAward' + resp = self.http.request(url, data={}) + if resp and resp.get('success'): + return resp.get('obj', {}) + 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_crush(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~dragonBoat2026TaskService~integralExchange' + data = { + "exchangeNum": 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 query_zongzi_status(self) -> Optional[Dict]: + """查询粽子状态(金币、粽子数量等)""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026ZongziService~queryStatus' + resp = self.http.request(url, data={}) + if resp and resp.get('success'): + return resp.get('obj', {}) + return None + + def crush_zongzi(self) -> Optional[Dict]: + """砸粽子""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026ZongziService~crush' + 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_daily_gift_status(self, city_code: str = '551') -> Optional[Dict]: + """查询每日礼包状态""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026DailyService~getDailyGiftStatus' + data = {"cityCode": city_code} + resp = self.http.request(url, data=data) + if resp and resp.get('success'): + return resp.get('obj', {}) + return None + + def query_extra_reward_cards(self) -> Optional[List]: + """查询额外奖励卡片(倒计时等)""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026ZongziService~queryExtraRewardCards' + resp = self.http.request(url, data={}) + if resp and resp.get('success'): + return resp.get('obj', []) + return None + + def get_family_status(self) -> Optional[Dict]: + """查询家庭权益状态""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026FamilyService~familyStatus' + resp = self.http.request(url, data={}) + if resp and resp.get('success'): + return resp.get('obj', {}) + 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 = "https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026IndexService~index" + data = {"inviteType": 1, "inviteUserId": random_invite} + resp = self.http.request(url, data=data) + self.logger.info(f"已向 {random_invite} 发起邀请") + except Exception as e: + self.logger.error(f"邀请初始化异常: {str(e)}") + + def get_invite_list(self) -> Optional[List[Dict]]: + """查询已邀请好友列表""" + url = 'https://mcs-mimp-web.sf-express.com/mcs-mimp/commonPost/~memberNonactivity~dragonBoat2026TaskService~taskInviteList' + 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 do_tasks(self, result: Dict) -> None: + """执行所有可自动完成的任务""" + 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 == 'INTEGRAL_EXCHANGE': + self.logger.task(f'[{task_name}] 尝试用积分兑换砸粽子次数...') + if self.exchange_points_for_crush(): + 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) + award = self.get_accrued_task_award() + if award: + progress = award.get('currentProgress', 0) + config = award.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})') + else: + self.logger.info(f'累计完成任务数: {progress}') + + def do_daily_gift(self, result: Dict) -> None: + """查看每日礼包状态""" + self.logger.info('查看每日礼包...') + time.sleep(1) + gift = self.get_daily_gift_status() + if gift: + if gift.get('received'): + products = gift.get('dailyGiftProductList', []) + if products: + names = ', '.join([p.get('productName', '未知') for p in products]) + self.logger.success(f'今日礼包已领取: {names}') + else: + self.logger.success('今日礼包已领取') + elif gift.get('canReceive'): + self.logger.info('每日礼包待领取(可能需手动领取)') + else: + self.logger.info('每日礼包暂不可领取') + else: + self.logger.warning('获取每日礼包状态失败') + + def do_extra_rewards(self, result: Dict) -> None: + """查询额外奖励卡片(倒计时等)""" + cards = self.query_extra_reward_cards() + if cards: + for card in cards: + extra_type = card.get('extraType', '') + latest_time = card.get('latestTime', '') + countdown = card.get('countdownHours', 0) + type_map = {'DAILY': '每日奖励', 'BUSINESS_COUPON': '商业券'} + type_name = type_map.get(extra_type, extra_type) + self.logger.info(f'额外奖励 [{type_name}]: 上次领取 {latest_time}, 倒计时 {countdown}h') + + def do_crush_zongzi(self, result: Dict) -> None: + """砸粽子直到没有粽子""" + # 先查看当前状态 + zongzi_status = self.query_zongzi_status() + if zongzi_status: + accounts = zongzi_status.get('currentAccountList', []) + gold_coin_balance = 0 + zongzi_balance = 0 + for acc in accounts: + if acc.get('currency') == 'GOLD_COIN': + gold_coin_balance = acc.get('balance', 0) + elif acc.get('currency') == 'GOLD_ZONGZI': + zongzi_balance = acc.get('balance', 0) + total_crush = zongzi_status.get('totalCrushTimes', 0) + + self.logger.info(f'当前金币: {gold_coin_balance}, 金粽子: {zongzi_balance}, 已砸次数: {total_crush}') + + if zongzi_balance <= 0: + self.logger.info('无金粽子可砸,跳过') + return + + # 开始砸粽子 + self.logger.info('开始砸粽子...') + crush_count = 0 + max_crush = 30 + + while crush_count < max_crush: + time.sleep(1) + crush_result = self.crush_zongzi() + if crush_result is None: + # 检查是否没粽子了 + current_status = self.query_zongzi_status() + if current_status: + for acc in current_status.get('currentAccountList', []): + if acc.get('currency') == 'GOLD_ZONGZI': + if acc.get('balance', 0) <= 0: + self.logger.info('金粽子已用完') + break + break + + received = crush_result.get('receivedAccountList', []) + current_accounts = crush_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['crush_detail'].append({'type': currency, 'amount': amount}) + result['gold_coin_earned'] += amount + crush_count += 1 + else: + self.logger.info('没有获得奖励') + break + + # 检查剩余粽子 + zongzi_balance = 0 + gold_coin_balance = 0 + for acc in current_accounts: + if acc.get('currency') == 'GOLD_ZONGZI': + zongzi_balance = acc.get('balance', 0) + elif acc.get('currency') == 'GOLD_COIN': + gold_coin_balance = acc.get('balance', 0) + + result['gold_coin_balance'] = gold_coin_balance + self.logger.info(f'剩余粽子: {zongzi_balance}, 金币: {gold_coin_balance}') + + if zongzi_balance <= 0: + break + + self.logger.info(f'砸粽子完成,本次共获得 {result["gold_coin_earned"]} 金币') + + + + def run(self) -> Dict[str, Any]: + """执行端午龙舟活动全流程""" + result = { + 'tasks_completed': 0, + 'gold_coin_earned': 0, + 'crush_detail': [], + 'gold_coin_balance': 0, + } + + # MODIFIED: 在任务开始前先发送邀请 + self.do_invite() + # ===== 查询并显示已邀请好友列表 ===== + 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', '未知') + self.logger.info(f" {i}. {mobile} (邀请时间: {invite_date})") + else: + self.logger.info("暂无已邀请好友") + # 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_daily_gift(result) + # 2. 额外奖励 + self.do_extra_rewards(result) + # 3. 执行任务 + self.do_tasks(result) + # 4. 查看累计奖励进度 + self.do_fetch_rewards(result) + # 5. 砸粽子 + self.do_crush_zongzi(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, 'gold_coin_earned': 0, 'crush_detail': [], 'gold_coin_balance': 0} + + masked_phone = phone[:3] + "****" + phone[7:] if len(phone) >= 7 else phone + logger.success(f'账号{index + 1}: 【{phone}】登录成功') + + # 随机延迟 + time.sleep(random.uniform(1, 3)) + + executor = DragonBoatExecutor(http, logger, user_id, invite_pool) + activity_result = executor.run() + + return { + 'success': True, + 'phone': phone, + 'index': index, + **activity_result, + } + + +# ==================== 主程序 ==================== +def main(): + # 收集所有 sfsyUrl 相关环境变量(兼容青龙面板同名多变量) + # 方式1:青龙可能把多条同名变量合并为一个,值用换行分隔 + # 方式2:青龙可能重命名为 sfsyUrl, sfsyUrl_1, sfsyUrl_2 ... + account_urls = [] + invite_pool = [] # 列表内互邀池 + + for key, value in os.environ.items(): + if key == 'sfsyUrl' or key.startswith('sfsyUrl_'): + # 青龙同名变量用 & 拼接,例如: url1&url2&url3 + # 每个账号格式: sessionId=xxx;_login_mobile_=xxx;_login_user_id_=xxx + # 拼接后: ...&sessionId=xxx;... + # 按 &sessionId= 拆分,首段已有 sessionId= 前缀,后续段补回 + parts = value.split('&sessionId=') + if parts[0].strip(): + account_urls.append(parts[0].strip()) + for p in parts[1:]: + u = ('sessionId=' + p).strip() + if u and u not in account_urls: + account_urls.append(u) + + if not account_urls: + print("未找到任何 sfsyUrl / sfsyUrl_* 环境变量,请检查青龙面板变量配置") + return + + # 从账号 URL 提取所有 user_id 组成邀请池 + for url in account_urls: + m = re.search(r'_login_user_id_=([A-Fa-f0-9]+)', url) + if m: + invite_pool.append(m.group(1)) + inviteId = invite_pool + print(f"邀请池: {len(invite_pool)} 个账号") + + vars_found = [k for k in os.environ if k == 'sfsyUrl' or k.startswith('sfsyUrl_')] + print(f"共获取到 {len(account_urls)} 个账号(来自环境变量: {vars_found})") + all_results = [] + + if CONCURRENT_NUM <= 1: + for idx, url in enumerate(account_urls): + print(f"\n{'='*60}") + print(f"开始处理第 {idx+1}/{len(account_urls)} 个账号") + print("="*60) + result = run_account(url, idx) + all_results.append(result) + if idx < len(account_urls) - 1: + 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("\n" + "=" * 60) + print(f"全部执行完毕,共处理 {len(all_results)} 个账号") + print("=" * 60) + for r in all_results: + status = "成功" if r['success'] else "失败" + phone = r.get('phone', '未知') + masked = phone[:3] + "****" + phone[7:] if len(phone) >= 7 else phone + print(f" [{status}] {masked} | 完成任务: {r['tasks_completed']} | 获得金币: {r['gold_coin_earned']}") + print("=" * 60) + +if __name__ == '__main__': + main() \ No newline at end of file