# cron: 30 8,17 * * * # new Env("王老吉瓶盖") #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 青龙面板微信协议自动化脚本 - 王老吉扫码抽奖 账号从环境变量 WLJ_ID 获取,格式:备注#wxid#手机号,多行分割 码字从同目录的 WLJ_MZ.txt 中获取 """ import os import sys import json import time import io import requests import logging import random import re import hashlib from datetime import datetime from typing import List, Dict, Any, Optional from urllib.parse import urlencode # ==================== 养鸡场配置 ==================== WX_CLOUD = os.getenv('wx_cloud', '') # 养鸡场服务地址 AUTH_TOKEN = os.getenv('wx_token', '') # 养鸡场认证Token (需要配置) # ==================== 码字文件配置 ==================== CODE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "WLJ_MZ.txt") # 码字文件路径(与脚本同目录) # ==================== 代理配置 ==================== PROXY_DEFAULT = "" # 代理API地址(返回格式:ip:端口),留空则不使用代理 PROXY_API_URL = "" # 青龙变量 pgdl(如果有值且脚本默认也为空时使用) USE_PROXY = False # 是否使用多账号代理(True=每个账号使用不同代理,False=不使用代理) # ==================== 账号配置 ==================== USE_API_ACCOUNTS = False # 是否从养鸡场API获取账号 → 固定关闭 WX_LIST_MANUAL = os.getenv("WLJ_ID", "") # 手动配置的账号列表 → 从环境变量 WLJ_ID 读取 # 需要剔除的wxid列表 REMOVE_WXIDS = [ "wxid_x4nz2s4th45k22", "wxid_fog306otfw9q22", "wxid_tso9447iuq0t22", "wxid_5jtncnh3v8ud2", ] DELAY_BETWEEN_ACCOUNTS = 8 # 账号间延迟(秒) WX_APPID = "wxd25dc8ba975776e3" # 小程序AppID(王老吉) # ==================== 抽奖配置 ==================== DAILY_LOTTERY_LIMIT = 1 # 每账号最大成功抽奖次数 MAX_CODE_ATTEMPTS = 1 # 每账号最大使用码次数(包括成功和失败的码) LOTTERY_INTERVAL_MIN = 5 # 最小抽奖间隔时间(秒) LOTTERY_INTERVAL_MAX = 15 # 最大抽奖间隔时间(秒) MAX_RETRY_COUNT = 3 # 最大重试次数 LOTTERY_CODE_PRICE = 0.35 # 有奖码统一价格(元) # ==================== API配置 ==================== WLJ_BASE_URL = "https://wechatec.brand.wljhealth.com" S3_BASE_URL = "https://s3.lsa0.cn" # 固定参数(从HAR抓包获取) POSSESSOR = "50c7d6a87202429a9871bf61ec85ad99" MALL_CODE = "wxd25dc8ba975776e3" SALE_CHANNEL = "mall" # ==================== 位置配置 ==================== # 扫码位置列表(经纬度),按顺序轮换 SCAN_LOCATIONS = [ ("28.228521", "112.939423"), # 湖南长沙 ("28.364827", "112.815102"), # 望城 ("28.157439", "113.632571"), # 浏阳 ("28.261334", "112.548920"), # 宁乡 ("27.832155", "113.158607"), # 株洲 ("27.672908", "113.497244"), # 醴陵 ("27.869672", "112.912388"), # 湘潭 ("27.538421", "112.287695"), # 韶山 ("27.751183", "112.503816"), # 湘乡 ("28.358012", "112.196543"), # 益阳 ("28.498775", "112.221098"), # 沅江 ("28.487346", "113.032817"), # 汨罗 ("28.683512", "112.869411"), # 湘阴 ("27.438907", "111.594236"), # 娄底 ] # ==================== 调试配置 ==================== DEBUG_MODE = False # 是否显示调试信息 True 开启 False 关闭 # ==================== User-Agent轮换池 ==================== USER_AGENTS = [ "Mozilla/5.0 (Linux; Android 14; HUAWEI Mate 60 RS Build/Mate60RS; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/935 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Honor Magic V2 Build/MagicV2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/870 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi 13 Build/13; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/882 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi Mix Fold 3 Build/MixFold3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/927 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Mate 70 Pro Build/Mate70Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/916 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Mate 70 Pro Build/Mate70Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/914 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OnePlus Ace 3 Pro Build/Ace3Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/905 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OnePlus 11 Build/11; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/859 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OPPO Find N3 Flip Build/FindN3Flip; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/928 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi 14 Pro Build/14Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/953 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Pura 70 Build/Pura70; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/958 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Honor Magic5 Build/Magic5; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/846 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OPPO Find X7 Ultra Build/FindX7Ultra; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/844 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Mate 70 Ultra Build/Mate70Ultra; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/823 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi Mix Flip Build/MixFlip; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/808 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI P60 Pro Build/P60Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/835 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Honor Magic6 Build/Magic6; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/845 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Honor Magic5 Build/Magic5; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/812 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Honor Magic6 RSR Build/Magic6RSR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/926 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OnePlus Ace 3 Build/Ace3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/806 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; vivo X90 Pro+ Build/X90ProPlus; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/912 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OPPO Find X7 Ultra Build/FindX7Ultra; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/886 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Pura 70 Pro Build/Pura70Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/831 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; vivo Fold 3 Build/Fold3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/949 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; HUAWEI Pura 80 Pro Build/Pura80Pro; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/848 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; vivo Fold 3 Build/Fold3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/901 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi Redmi K70 Build/RedmiK70; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/955 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OnePlus Ace 3 Build/Ace3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/818 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi Mix Fold 3 Build/MixFold3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/805 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OPPO Find X7 Ultra Build/FindX7Ultra; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/875 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; Xiaomi Mix Flip Build/MixFlip; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/950 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android", "Mozilla/5.0 (Linux; Android 14; OPPO Find X7 Ultra Build/FindX7Ultra; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.173 Mobile Safari/537.36 XWEB/1420273 MMWEBSDK/20250201 MMWEBID/883 MicroMessenger/8.0.60.2860(0x28003C55) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android", ] # ==================== 设备型号 ==================== DEVICE_MODELS = [ "Xiaomi-MixFold3", "Huawei-Mate60RS", "Huawei-P60", "Honor-Magic6RSR", "Honor-Magic5", "Huawei-Nova12Pro", "Xiaomi-RedmiK70Pro", "Huawei-Pura70", "Huawei-Pura70Pro", "OPPO-Reno11", "OnePlus-Ace3Pro", "Huawei-Nova12", "OPPO-FindX7Ultra", "vivo-X90ProPlus", "Huawei-Mate70Pro+", "Huawei-MateX3", "Xiaomi-14Ultra", "Huawei-Mate70RS", "OPPO-Reno11Pro", "vivo-X100Ultra", "OPPO-FindN3Flip", "OnePlus-Ace3", "Huawei-Pura80", "Huawei-Pura80Pro", "Huawei-Mate60Pro+", "Huawei-P60Pro+", "Huawei-MateX5", "Xiaomi-13", "Xiaomi-MixFlip", "Xiaomi-RedmiK60Pro", "vivo-X100", "Xiaomi-RedmiK60", ] # 自定义日志格式 class SimpleFormatter(logging.Formatter): def format(self, record): return record.getMessage() handler = logging.StreamHandler(io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")) handler.setFormatter(SimpleFormatter()) logging.basicConfig(level=logging.INFO, handlers=[handler]) logger = logging.getLogger(__name__) class WangLaoJiAutomation: def __init__(self): self.session = requests.Session() self.wx_accounts = [] self.daily_lottery_limit = DAILY_LOTTERY_LIMIT self.delay_between_accounts = DELAY_BETWEEN_ACCOUNTS self.lottery_interval_min = LOTTERY_INTERVAL_MIN self.lottery_interval_max = LOTTERY_INTERVAL_MAX # 待使用的码字(失败后顺延,下次优先使用) self.pending_code = None # 运行时读取代理配置 env_proxy = os.getenv('pgdl', '') if PROXY_DEFAULT: self.proxy_url = PROXY_DEFAULT logger.info(f"🌐 代理来源: 脚本配置") if env_proxy: logger.info(f"🌐 青龙变量 pgdl 已设置但未使用") elif env_proxy: self.proxy_url = env_proxy logger.info(f"🌐 代理来源: 青龙变量 pgdl") else: self.proxy_url = "" logger.info(f"⚠️ 未配置代理(脚本和青龙变量都为空)") self.use_proxy = USE_PROXY self.current_proxy = None self.codes_exhausted = False self.scan_loc_index = 0 # 位置轮换索引 self.ua_index = 0 # UA轮换索引 self.ssl_error_count = 0 # SSL错误计数,连续2次则重新获取IP self.load_config() self.setup_clients() logger.info("🏮 王老吉扫码抽奖 - 自动化脚本启动") logger.info(f"👥 账号数量: {len(self.wx_accounts)}") logger.info(f"🎯 每账号执行: {self.daily_lottery_limit} 次抽奖") logger.info(f"📄 码字文件: {CODE_FILE}") if self.use_proxy and self.proxy_url: logger.info(f"🌐 代理模式: 开启") # ==================== 码字文件操作 ==================== def read_codes_from_file(self) -> List[str]: """从文件读取所有码字""" try: if os.path.exists(CODE_FILE): with open(CODE_FILE, 'r', encoding='utf-8') as f: lines = [line.strip() for line in f if line.strip()] return lines else: logger.error(f"❌ 码字文件不存在: {CODE_FILE}") return [] except Exception as e: logger.error(f"❌ 读取码字文件失败: {e}") return [] def remove_used_code(self, code: str) -> bool: """从文件中删除已使用的码字""" try: codes = self.read_codes_from_file() if code in codes: codes.remove(code) with open(CODE_FILE, 'w', encoding='utf-8') as f: f.write('\n'.join(codes)) logger.info(f"✅ 已从文件删除码字: {code} (剩余: {len(codes)} 个)") return True else: logger.warning(f"⚠️ 码字不在文件中: {code}") return False except Exception as e: logger.error(f"❌ 删除码字失败: {e}") return False def get_pending_code(self) -> Optional[str]: """获取待重试的码字""" code = self.pending_code if code: logger.info(f"🔄 复用上次失败的码字: {code}") self.pending_code = None return code def set_pending_code(self, code: str): """设置待重试的码字(失败时保留)""" if code: logger.warning(f"⚠️ 码字失败,顺延下次使用: {code}") self.pending_code = code def commit_scan_code(self, code: str): """确认码字使用成功,从文件删除""" self.remove_used_code(code) # ==================== 代理相关 ==================== def get_proxy(self) -> Optional[str]: """获取代理""" if not self.proxy_url: return None try: response = requests.get(self.proxy_url, timeout=10) if response.status_code == 200: try: data = response.json() if isinstance(data, dict) and "data" in data: proxy_list = data["data"] if isinstance(proxy_list, list) and len(proxy_list) > 0: first_proxy = proxy_list[0] ip = first_proxy.get("ip") port = first_proxy.get("port") if ip and port: proxy = f"{ip}:{port}" logger.info(f"🌐 获取代理成功: {proxy}") return proxy except: pass proxy = response.text.strip() if proxy: logger.info(f"🌐 获取代理成功: {proxy}") return proxy except Exception as e: logger.error(f"🌐 获取代理失败: {e}") return None def get_scan_location(self) -> tuple: """获取轮换的扫码位置""" lat, lon = SCAN_LOCATIONS[self.scan_loc_index % len(SCAN_LOCATIONS)] self.scan_loc_index += 1 return lat, lon def get_ua(self) -> str: """获取轮换的User-Agent""" ua = USER_AGENTS[self.ua_index % len(USER_AGENTS)] self.ua_index += 1 return ua def get_proxies(self, proxy: Optional[str] = None) -> Optional[dict]: """获取代理字典格式""" if proxy: return {"http": f"http://{proxy}", "https": f"http://{proxy}"} return None @staticmethod def is_ssl_error(error_str: str) -> bool: """检测是否为SSL错误""" ssl_keywords = ['ssl', 'eof', 'SSLEOF', 'EOF occurred', 'ssl.c:', 'SSL'] return any(keyword in error_str.lower() for keyword in ssl_keywords) def refresh_proxy_ip(self) -> Optional[str]: """重新获取代理IP""" self.ssl_error_count = 0 # 重置计数 new_proxy = self.get_proxy() if new_proxy: logger.info(f"🔄 SSL错误达到2次,重新获取IP: {new_proxy}") return new_proxy logger.error(f"❌ 重新获取IP失败") return None def load_config(self): """加载配置""" self.wx_accounts = self.parse_manual_accounts() def setup_clients(self): """设置HTTP客户端""" self.session.headers.update({ "Content-Type": "application/x-www-form-urlencoded", "charset": "utf-8", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://servicewechat.com/wxd25dc8ba975776e3/409/page-frame.html", }) def parse_manual_accounts(self) -> List[Dict[str, str]]: """解析从环境变量 WLJ_ID 读取的账号""" accounts = [] account_text = os.getenv("WLJ_ID", "").strip() if not account_text: logger.error("❌ 未配置环境变量 WLJ_ID") return accounts for line in account_text.splitlines(): line = line.strip() if not line: continue parts = line.split("#") if len(parts) >= 3: accounts.append({ "nickname": parts[0], "wxId": parts[1], "phone": parts[2], "wxName": parts[0], }) accounts = [a for a in accounts if a.get("wxId") not in REMOVE_WXIDS] logger.info(f"✅ 从 WLJ_ID 加载账号 {len(accounts)} 个") return accounts def get_code_from_chicken_farm(self, wxid: str = None) -> Optional[str]: """从养鸡场获取微信code""" try: url = f"{WX_CLOUD}/prod-api/wechat/api/getMiniProgramCode" headers = { "Authorization": AUTH_TOKEN, "Content-Type": "application/json", } payload = { "wxid": wxid if wxid else "", "appid": WX_APPID } if DEBUG_MODE: logger.info(f"📤 养鸡场API: {url}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, timeout=10) response.raise_for_status() res_data = response.json() if DEBUG_MODE: logger.info(f"📥 养鸡场返回: {res_data}") if res_data.get("code") == 200 and res_data.get("data", {}).get("code"): code = res_data["data"]["code"] logger.info(f"✅ 获取微信code成功: {code}") return code else: logger.warning(f"⚠️ 养鸡场无可用code") return None except Exception as e: logger.error(f"❌ 获取code失败: {e}") return None def get_scan_code(self) -> Optional[str]: """获取扫码码(优先待重试的码,其次从文件读取)""" # 1. 优先使用待重试的码字 pending = self.get_pending_code() if pending: return pending # 2. 检查文件是否还有码字 if self.codes_exhausted: logger.warning(f"⚠️ 扫码码已用完,不再获取") return None codes = self.read_codes_from_file() if codes: code = codes[0] remaining = len(codes) - 1 logger.info(f"✅ 从文件获取扫码码: {code} (剩余: {remaining} 个)") return code else: logger.warning(f"⚠️ 文件中无码字") self.codes_exhausted = True return None @staticmethod def compute_sign(user_code: str) -> str: """计算sign: MD5(userCode + 'cU9(yZ3{zD6!pE4.xX7#')""" secret = "cU9(yZ3{zD6!pE4.xX7#" raw = (user_code or "") + secret return hashlib.md5(raw.encode('utf-8')).hexdigest().upper() def step1_wlj_login(self, code: str, proxy: Optional[str] = None) -> Optional[Dict]: """步骤1: 微信code换取wljhealth登录token""" url = f"{WLJ_BASE_URL}/userInfoMini/userMemberLogin" ua = self.get_ua() # sign: MD5(userCode + secret), userCode初始为空 user_code = "" sign = self.compute_sign(user_code) data = { "code": code, "possessor": POSSESSOR, "userCode": user_code, "mallCode": MALL_CODE, "saleChannel": SALE_CHANNEL, "sign": sign, } proxies = self.get_proxies(proxy) headers = { "Content-Type": "application/x-www-form-urlencoded", "charset": "utf-8", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://servicewechat.com/wxd25dc8ba975776e3/409/page-frame.html", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求头: {headers}") logger.info(f"📤 请求体: {data}") body = urlencode(data).encode('utf-8') response = self.session.post(url, data=body, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应状态码: {response.status_code}") logger.info(f"📥 响应体: {response.text}") logger.info(f"📥 响应Set-Cookie: {response.headers.get('Set-Cookie', '无')}") result = response.json() if result.get("success") and result.get("status") == 1: content = result["content"] # content可能是字符串(错误信息)也可能是字典 if isinstance(content, str): logger.error(f"❌ 步骤1登录失败: {content}") return None if not isinstance(content, dict): logger.error(f"❌ 步骤1登录失败: content类型异常 {type(content)}") return None token = content.get("token", "") # user_summary 嵌套在里面 user_summary = content.get("user_summary", {}) openid = user_summary.get("openid", "") user_code = user_summary.get("userCode", "") user_nickname = user_summary.get("userNickname", "") phone = user_summary.get("phone", "") or user_summary.get("userPhone", "") logger.info(f"✅ 步骤1成功 - OpenID: {openid}, 昵称: {user_nickname}, 手机: {phone}") return { "token": token, "openid": openid, "userCode": user_code, "userNickname": user_nickname, "phone": phone, } else: logger.error(f"❌ 步骤1登录失败: {result.get('msg')}") return None except Exception as e: logger.error(f"❌ 步骤1请求失败: {e}") return None def step2_get_s3_token(self, wlj_token: str, serial_code: str, user_code: str, scan_lat: str, scan_lon: str, proxy: Optional[str] = None) -> Optional[str]: """步骤2: 用wljtoken换取s3.lsa0.cn的token""" url = f"{WLJ_BASE_URL}/openapi/getToken" ua = self.get_ua() # HAR中getToken没有sign参数,userCode从step1获取 form_data = { "serialCode": serial_code, "latitude": scan_lat, "longitude": scan_lon, "possessor": POSSESSOR, "userCode": user_code, "mallCode": MALL_CODE, "saleChannel": SALE_CHANNEL, } proxies = self.get_proxies(proxy) headers = { "Content-Type": "application/x-www-form-urlencoded", "charset": "utf-8", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://servicewechat.com/wxd25dc8ba975776e3/409/page-frame.html", "User-Agent": ua, "Authorization": wlj_token, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求头: {headers}") logger.info(f"📤 请求体: {form_data}") body = urlencode(form_data).encode('utf-8') response = self.session.post(url, data=body, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 实际请求Cookie: {self.session.cookies.get_dict()}") logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("status") == 1: s3_token = result["content"] logger.info(f"✅ 步骤2成功 - 获取s3 token") return s3_token else: logger.error(f"❌ 步骤2获取s3token失败: {result.get('msg')}") return None except Exception as e: logger.error(f"❌ 步骤2请求失败: {e}") return None def step3_s3_third_login(self, s3_token: str, proxy: Optional[str] = None) -> Optional[str]: """步骤3: s3第三方登录,获取最终s3 Bearer token""" url = f"{S3_BASE_URL}/openapi/promotion/consumer/auth/thirdLogin" payload = { "appid": WX_APPID, "sign": s3_token, } proxies = self.get_proxies(proxy) ua = self.get_ua() headers = { "Authorization": f"Bearer {s3_token}", "Content-Type": "application/json", "Origin": "https://s3.lsa0.cn", "X-Requested-With": "com.tencent.mm", "Referer": "https://s3.lsa0.cn/", "Accept-Encoding": "gzip, deflate, br", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求头: {headers}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("code") == "200": final_token = result["data"]["token"] open_id = result["data"].get("openId", "") logger.info(f"✅ 步骤3成功 - s3登录成功") return final_token, False else: logger.error(f"❌ 步骤3 s3登录失败: {result.get('message')}") return None, False except Exception as e: error_str = str(e) logger.error(f"❌ 步骤3请求失败: {e}") # 判断是否为SSL错误 is_ssl = self.is_ssl_error(error_str) if is_ssl: logger.warning(f"⚠️ SSL错误,码字将顺延下次使用") return None, is_ssl def step4_scan_mask_code(self, s3_token: str, mask_code: str, scan_lat: str, scan_lon: str, proxy: Optional[str] = None) -> tuple: """步骤4: 扫码核销 返回 (结果, 是否代理错误, 是否每日上限)""" url = f"{S3_BASE_URL}/openapi/promotion/campaignExecute/scanMaskCode" payload = { "maskCode": mask_code, "lng": scan_lon, "lat": scan_lat, "lastScanResult": False, } proxies = self.get_proxies(proxy) ua = self.get_ua() headers = { "Authorization": f"Bearer {s3_token}", "Content-Type": "application/json", "Origin": "https://s3.lsa0.cn", "X-Requested-With": "com.tencent.mm", "Referer": "https://s3.lsa0.cn/", "Accept-Encoding": "gzip, deflate, br", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求头: {headers}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("code") == "200": data = result["data"] biz_code = data.get("bizCode", "") biz_message = data.get("bizMessage", "") auto_lottery = data.get("autoLottery", False) campaign_name = data.get("campaignName", "") # 每日上限检测优先判断 if "已超上限" in biz_message or "超限" in biz_message: logger.warning(f"⚠️ 步骤4扫码 - 每日次数已上限: {biz_message}") return data, False, True if biz_code == "0000000": logger.info(f"✅ 步骤4扫码成功 - 活动: {campaign_name}, 状态: {biz_message}, 自动抽奖: {auto_lottery}") return data, False, False else: logger.error(f"❌ 步骤4扫码失败 - bizCode: {biz_code}, msg: {biz_message}") return None, False, False else: logger.error(f"❌ 步骤4扫码失败: {result.get('message')}") return None, False, False except Exception as e: error_str = str(e) logger.error(f"❌ 步骤4请求失败: {e}") is_proxy_error = any(pe in error_str for pe in ['Proxy', 'timeout', 'timed out', 'Cannot connect']) return None, is_proxy_error, False def step5_mask_code_lottery(self, s3_token: str, mask_code: str, scan_lat: str, scan_lon: str, proxy: Optional[str] = None) -> tuple: """步骤5: 抽奖 返回 (结果, 是否代理错误)""" url = f"{S3_BASE_URL}/openapi/promotion/campaignExecute/maskCodeLottery" payload = { "maskCode": mask_code, "lng": scan_lon, "lat": scan_lat, } proxies = self.get_proxies(proxy) ua = self.get_ua() headers = { "Authorization": f"Bearer {s3_token}", "Content-Type": "application/json", "Origin": "https://s3.lsa0.cn", "X-Requested-With": "com.tencent.mm", "Referer": "https://s3.lsa0.cn/", "Accept-Encoding": "gzip, deflate, br", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求头: {headers}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("code") == "200": data = result["data"] biz_code = data.get("bizCode", "") biz_message = data.get("bizMessage", "") lucky_list = data.get("luckyRewardList", []) campaign_name = data.get("campaignName", "") if biz_code == "0000000": logger.info(f"✅ 步骤5抽奖成功 - 活动: {campaign_name}, 结果: {biz_message}") return data, False else: logger.error(f"❌ 步骤5抽奖失败 - bizCode: {biz_code}, msg: {biz_message}") return None, False else: logger.error(f"❌ 步骤5抽奖失败: {result.get('message')}") return None, False except Exception as e: error_str = str(e) logger.error(f"❌ 步骤5请求失败: {e}") is_proxy_error = any(pe in error_str for pe in ['Proxy', 'timeout', 'timed out', 'Cannot connect']) return None, is_proxy_error def step6_receive_reward(self, s3_token: str, user_reward_id: str, proxy: Optional[str] = None) -> tuple: """步骤6: 领取红包(先调用receiveReward)""" url = f"{S3_BASE_URL}/openapi/promotion/consumer/receiveReward" payload = {"userRewardId": user_reward_id} proxies = self.get_proxies(proxy) ua = self.get_ua() headers = { "Authorization": f"Bearer {s3_token}", "Content-Type": "application/json", "Origin": "https://s3.lsa0.cn", "X-Requested-With": "com.tencent.mm", "Referer": "https://s3.lsa0.cn/", "Accept-Encoding": "gzip, deflate, br", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("code") == "200": biz_code = result.get("data", {}).get("bizCode", "") biz_msg = result.get("data", {}).get("bizMessage", "") inner_data = result.get("data", {}).get("data", {}) claim_end = inner_data.get("claimEndTime", "") auto_withdraw = inner_data.get("autoWithdraw", False) if biz_code == "0000000": logger.info(f"✅ 步骤6领取红包成功 - 领取截止: {claim_end}, 自动提现: {auto_withdraw}") return inner_data, False else: logger.error(f"❌ 步骤6领取红包失败 - bizCode: {biz_code}, msg: {biz_msg}") return None, False else: logger.error(f"❌ 步骤6领取红包失败: {result.get('message')}") return None, False except Exception as e: error_str = str(e) logger.error(f"❌ 步骤6请求失败: {e}") is_proxy_error = any(pe in error_str for pe in ['Proxy', 'timeout', 'timed out', 'Cannot connect']) return None, is_proxy_error def step7_claim_reward(self, s3_token: str, user_reward_id: str, proxy: Optional[str] = None) -> tuple: """步骤7: 确认领取奖励(后调用claimReward)""" url = f"{S3_BASE_URL}/openapi/promotion/consumer/claimReward" payload = {"id": user_reward_id} proxies = self.get_proxies(proxy) ua = self.get_ua() headers = { "Authorization": f"Bearer {s3_token}", "Content-Type": "application/json", "Origin": "https://s3.lsa0.cn", "X-Requested-With": "com.tencent.mm", "Referer": "https://s3.lsa0.cn/", "Accept-Encoding": "gzip, deflate, br", "User-Agent": ua, } try: if DEBUG_MODE: logger.info(f"📤 请求URL: {url}") logger.info(f"📤 请求体: {payload}") response = requests.post(url, json=payload, headers=headers, proxies=proxies, timeout=10) response.raise_for_status() if DEBUG_MODE: logger.info(f"📥 响应体: {response.text}") result = response.json() if result.get("success") and result.get("code") == "200": biz_code = result.get("data", {}).get("bizCode", "") biz_msg = result.get("data", {}).get("bizMessage", "") if biz_code == "0000000": logger.info(f"✅ 步骤7确认领取成功 - {biz_msg}") return result.get("data", {}), False else: logger.error(f"❌ 步骤7确认领取失败 - bizCode: {biz_code}, msg: {biz_msg}") return None, False else: logger.error(f"❌ 步骤7确认领取失败: {result.get('message')}") return None, False except Exception as e: error_str = str(e) logger.error(f"❌ 步骤7请求失败: {e}") is_proxy_error = any(pe in error_str for pe in ['Proxy', 'timeout', 'timed out', 'Cannot connect']) return None, is_proxy_error def process_account(self, account: Dict) -> Dict: """处理单个账号""" result = { "nickname": account.get("wxName", ""), "wxid": account.get("wxId", ""), "phone": account.get("phone", ""), "lottery_count": 0, "code_used_count": 0, "prizes": [], "total_amount": 0.0, "success": False, } # 重置SSL错误计数(每个账号独立计算) self.ssl_error_count = 0 wxid = account.get("wxId", "") # 获取代理 account_proxy = None if self.use_proxy: account_proxy = self.get_proxy() if account_proxy: logger.info(f"🌐 账号使用代理: {account_proxy}") # ====== 完整登录流程(获取s3 token)====== # 步骤1: 获取微信code并登录wljhealth login_code = self.get_code_from_chicken_farm(wxid) if not login_code: logger.error(f"❌ 无法获取微信code,跳过账号") return result # 步骤1: wljhealth登录 login_result = None retry_count = 0 while retry_count < MAX_RETRY_COUNT and not login_result: login_result = self.step1_wlj_login(login_code, account_proxy) if not login_result: if self.use_proxy and account_proxy: logger.warning(f"⚠️ wlj登录失败,尝试更换代理...") new_proxy = self.get_proxy() if new_proxy: account_proxy = new_proxy logger.info(f"🌐 更换新代理: {account_proxy}") login_result = self.step1_wlj_login(login_code, account_proxy) if login_result: break if not login_result and retry_count < MAX_RETRY_COUNT - 1: logger.warning(f"⚠️ wlj登录失败,获取新code重试 ({retry_count + 1}/{MAX_RETRY_COUNT})") login_code = self.get_code_from_chicken_farm(wxid) if login_code: retry_count += 1 time.sleep(1) continue else: logger.error(f"❌ 无法获取新code") break else: break if not login_result: logger.error(f"❌ 登录失败,已重试{retry_count}次,跳过账号") return result wlj_token = login_result["token"] logger.info(f"✅ 登录完成 - 用户: {login_result.get('userNickname', '')}, 手机: {login_result.get('phone', '')}") # ====== 抽奖主循环 ====== success_lottery_count = 0 code_used_count = 0 current_proxy = account_proxy s3_token = None # 缓存s3 token s3_token_code = None # 关联的serialCode # 每个账号固定一个经纬度 scan_lat, scan_lon = self.get_scan_location() logger.info(f"📍 账号扫码位置 - 纬度: {scan_lat}, 经度: {scan_lon}") while success_lottery_count < self.daily_lottery_limit: if code_used_count >= MAX_CODE_ATTEMPTS: logger.warning(f"⚠️ 已使用{code_used_count}个码,退出循环") break logger.info(f"📝 第 {success_lottery_count + 1}/{self.daily_lottery_limit} 次成功抽奖 (已使用 {code_used_count} 个码)") # 获取扫码码(优先待重试的码) scan_code = self.get_scan_code() if not scan_code: logger.error(f"❌ 无法获取扫码码") break # 步骤2: 获取s3 token(需要serialCode) s3_token = self.step2_get_s3_token(wlj_token, scan_code, login_result.get("userCode", ""), scan_lat, scan_lon, current_proxy) if not s3_token: logger.warning(f"⚠️ 获取s3token失败,码字顺延") self.set_pending_code(scan_code) # 保留码字 interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) continue # 步骤3: s3第三方登录 final_s3_token, is_ssl = self.step3_s3_third_login(s3_token, current_proxy) # 处理SSL错误 if is_ssl: self.ssl_error_count += 1 logger.warning(f"⚠️ SSL错误 ({self.ssl_error_count}/2),码字顺延") self.set_pending_code(scan_code) # 保留码字 if self.ssl_error_count >= 2: # 连续2次SSL错误,重新获取IP new_proxy = self.refresh_proxy_ip() if new_proxy: current_proxy = new_proxy else: logger.error(f"❌ 无法重新获取IP,退出") break interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) continue if not final_s3_token: self.ssl_error_count = 0 # 非SSL错误,重置计数 logger.warning(f"⚠️ s3登录失败,码字顺延") self.set_pending_code(scan_code) # 保留码字,不计入已使用 interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) continue # 步骤4: 扫码核销 scan_result, is_proxy_error, is_daily_limit = self.step4_scan_mask_code(final_s3_token, scan_code, scan_lat, scan_lon, current_proxy) if is_daily_limit: # 每日上限,不删除码字(可以下次再用) logger.warning(f"⚠️ 每日上限,停止此账号") break if not scan_result: if self.use_proxy and is_proxy_error: logger.warning(f"⚠️ 扫码代理错误,尝试更换代理...") new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy logger.info(f"🌐 更换新代理: {current_proxy}") # 重新获取s3 token并扫码 s3_token = self.step2_get_s3_token(wlj_token, scan_code, login_result.get("userCode", ""), scan_lat, scan_lon, current_proxy) if s3_token: final_s3_token = self.step3_s3_third_login(s3_token, current_proxy) if final_s3_token: scan_result, _, _ = self.step4_scan_mask_code(final_s3_token, scan_code, scan_lat, scan_lon, current_proxy) if scan_result: # 扫码成功后再检查一遍每日上限 scan_biz_msg = scan_result.get("bizMessage", "") if "已超上限" in scan_biz_msg or "超限" in scan_biz_msg: logger.warning(f"⚠️ {scan_biz_msg},停止此账号") break if not scan_result: logger.warning(f"⚠️ 扫码失败,码字顺延") self.set_pending_code(scan_code) # 保留码字 interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) continue # ====== 扫码成功,使用码字并抽奖 ====== code_used_count += 1 result["code_used_count"] = code_used_count # 步骤5: 抽奖 lottery_result, is_lottery_proxy_error = self.step5_mask_code_lottery(final_s3_token, scan_code, scan_lat, scan_lon, current_proxy) if not lottery_result and self.use_proxy and is_lottery_proxy_error: logger.warning(f"⚠️ 抽奖代理错误,尝试更换代理...") new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy logger.info(f"🌐 更换新代理: {current_proxy}") lottery_result, _ = self.step5_mask_code_lottery(final_s3_token, scan_code, scan_lat, scan_lon, current_proxy) if lottery_result is not None: lucky_list = lottery_result.get("luckyRewardList", []) # 只有抽中奖品(有luckyRewardList)才计入成功次数 if lucky_list: success_lottery_count += 1 result["lottery_count"] = success_lottery_count if lucky_list: for prize in lucky_list: prize_name = prize.get("rewardName", "未知") reward_value = prize.get("rewardValue", 0) user_reward_id = prize.get("userRewardId", "") reward_type = prize.get("rewardType", "") result["prizes"].append(prize_name) logger.info(f"🎁 奖品: {prize_name}, 价值: {reward_value/100:.2f}元") # 领取奖励(步骤6+7)- 只有红包类型且incentive=true(激活)才领取 custom_data = prize.get("customData", {}) incentive = custom_data.get("incentive", False) if user_reward_id and reward_type == "redpacket": if incentive: logger.info(f"💰 红包已激活,执行领取流程") claim_data, is_claim_error = self.step6_receive_reward(final_s3_token, user_reward_id, current_proxy) if not claim_data and self.use_proxy and is_claim_error: new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy claim_data, _ = self.step6_receive_reward(final_s3_token, user_reward_id, current_proxy) if claim_data: receive_data, is_receive_error = self.step7_claim_reward(final_s3_token, user_reward_id, current_proxy) if not receive_data and self.use_proxy and is_receive_error: new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy self.step7_claim_reward(final_s3_token, user_reward_id, current_proxy) if receive_data: result["total_amount"] += float(reward_value) / 100.0 logger.info(f"💰 红包已到账: {reward_value/100:.2f}元") else: logger.info(f"⏳ 红包未激活,等待下次扫码激活") else: logger.info(f"✅ 复购类型,不计入金额") else: biz_message = lottery_result.get("bizMessage", "") logger.info(f"🎁 抽奖结果: {biz_message}") else: interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) # ====== 抽奖完成,从文件删除码字 ====== self.commit_scan_code(scan_code) # 间隔时间 if success_lottery_count < self.daily_lottery_limit: interval = random.uniform(self.lottery_interval_min, self.lottery_interval_max) time.sleep(interval) result["success"] = result["lottery_count"] > 0 return result def run(self): """运行主流程""" if not self.wx_accounts: logger.error("❌ 没有可用账号") return all_results = [] total_lottery = 0 total_amount = 0.0 success_count = 0 for idx, account in enumerate(self.wx_accounts): nickname = account.get('wxName', '未知') logger.info(f"\n========== 账号 {idx+1}/{len(self.wx_accounts)} 🏮 {nickname} ==========") result = self.process_account(account) all_results.append(result) total_lottery += result["lottery_count"] total_amount += result["total_amount"] if result["success"]: success_count += 1 account_cost = result.get("code_used_count", result["lottery_count"]) * LOTTERY_CODE_PRICE account_profit = result["total_amount"] - account_cost logger.info(f"💰 账号 {idx+1} 成功: {result['lottery_count']}次 | 用码: {result.get('code_used_count', 0)}个 | 金额: {result['total_amount']:.2f}元 | 成本: {account_cost:.2f}元 | 利润: {account_profit:.2f}元") if self.codes_exhausted: logger.warning(f"⚠️ 扫码码已用完,停止后续账号") break if idx < len(self.wx_accounts) - 1: logger.info(f"⏳ 等待 {self.delay_between_accounts} 秒后处理下一个账号...") time.sleep(self.delay_between_accounts) self.print_summary(all_results, total_lottery, total_amount, success_count) def print_summary(self, results: List[Dict], total_lottery: int, total_amount: float, success_count: int): """打印汇总报告""" total_code_used = sum(r.get("code_used_count", r.get("lottery_count", 0)) for r in results) logger.info("\n" + "="*50) logger.info("📊 执行汇总") logger.info("="*50) logger.info(f"👥 总账号数: {len(results)}") logger.info(f"✅ 成功账号: {success_count}") logger.info(f"🎰 总成功抽奖: {total_lottery} 次") logger.info(f"📱 总用码: {total_code_used} 个") logger.info(f"💰 总金额: {total_amount:.2f} 元") cost = total_code_used * LOTTERY_CODE_PRICE profit = total_amount - cost logger.info(f"💵 成本: {cost:.2f} 元") logger.info(f"📈 利润: {profit:.2f} 元") logger.info("\n📋 账号详情:") for i, r in enumerate(results): status = "✅" if r["success"] else "❌" prize_info = ", ".join(r["prizes"]) if r["prizes"] else "无" logger.info(f" {status} 账号{i+1}: {r['nickname']} | 抽奖: {r['lottery_count']}次 | 金额: {r['total_amount']:.2f}元 | 奖品: {prize_info}") def main(): """主入口""" automation = WangLaoJiAutomation() automation.run() if __name__ == "__main__": main()