# cron: 12 8 * * * # new Env("王老吉瓶盖") #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 青龙面板微信协议自动化脚本 - 王老吉扫码抽奖 账号从环境变量 WLJ_ID 获取,格式:备注#wxid#手机号,多行分割 码字从同目录 WLJ_MZ.txt 文件中获取,每行一个码,用完自动删除 抽奖参数从环境变量 WLJ_VT 获取,格式:上限#最大用码#最小间隔#最大间隔#成本 """ import os import sys import io import time import json import random import hashlib import requests import logging from typing import Optional, List, Dict, Any from urllib.parse import urlencode # ==================== 养鸡场配置 ==================== WX_CLOUD = os.getenv('wx_cloud', '') AUTH_TOKEN = os.getenv('wx_token', '') # ==================== 代理配置 ==================== PROXY_DEFAULT = "" PROXY_API_URL = "" USE_PROXY = True # ==================== 抽奖参数(从环境变量 WLJ_VT 整合) ==================== WLJ_VT_STR = os.getenv('WLJ_VT', '2#5#5#10#0.34') try: parts = WLJ_VT_STR.split('#') DAILY_LOTTERY_LIMIT = int(parts[0]) MAX_CODE_ATTEMPTS = int(parts[1]) LOTTERY_INTERVAL_MIN = float(parts[2]) LOTTERY_INTERVAL_MAX = float(parts[3]) LOTTERY_CODE_PRICE = float(parts[4]) except: DAILY_LOTTERY_LIMIT = 2 MAX_CODE_ATTEMPTS = 5 LOTTERY_INTERVAL_MIN = 5 LOTTERY_INTERVAL_MAX = 10 LOTTERY_CODE_PRICE = 0.34 # ==================== 账号配置 ==================== WX_LIST_MANUAL = os.getenv("WLJ_ID", "") REMOVE_WXIDS = [ "wxid_x4nz2s4th45k22", "wxid_fog306otfw9q22", "wxid_tso9447iuq0t22", "wxid_5jtncnh3v8ud2", ] DELAY_BETWEEN_ACCOUNTS = 3 WX_APPID = "wxd25dc8ba975776e3" # ==================== API配置 ==================== WLJ_BASE_URL = "https://wechatec.brand.wljhealth.com" S3_BASE_URL = "https://s3.lsa0.cn" 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"), ] # ==================== 码字文件路径 ==================== CODE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "WLJ_MZ.txt") # ==================== 调试模式 ==================== DEBUG_MODE = 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 elif env_proxy: self.proxy_url = env_proxy else: self.proxy_url = "" self.use_proxy = USE_PROXY and bool(self.proxy_url) self.current_proxy = None self.codes_exhausted = False self.scan_loc_index = 0 self.ua_index = 0 self.ssl_error_count = 0 # 码字从文件加载 self.code_list = self.read_codes_from_file() self.load_config() self.setup_clients() logger.info("🏮 王老吉扫码抽奖启动") logger.info(f"👥 账号: {len(self.wx_accounts)} | 🎯 每号抽奖: {self.daily_lottery_limit} 次 | 🔢 码字剩余: {len(self.code_list)}") if self.use_proxy: logger.info(f"🌐 代理已启用") # ==================== 码字文件管理 ==================== def read_codes_from_file(self) -> List[str]: """从 WLJ_MZ.txt 读取所有码字""" try: if os.path.exists(CODE_FILE): with open(CODE_FILE, 'r', encoding='utf-8') as f: codes = [line.strip() for line in f if line.strip()] return codes else: logger.error(f"❌ 码字文件不存在: {CODE_FILE}") return [] except Exception as e: logger.error(f"❌ 读取码字文件失败: {e}") return [] def remove_code_from_file(self, code: str): """从 WLJ_MZ.txt 中删除指定码字""" 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"✅ 已从文件删除码字,剩余 {len(codes)} 个") else: logger.warning(f"⚠️ 码字不在文件中: {code}") except Exception as e: logger.error(f"❌ 删除码字失败: {e}") def get_scan_code(self) -> Optional[str]: """获取一个码字(优先待重试的,其次从列表第一个取)""" if self.pending_code: code = self.pending_code self.pending_code = None logger.info(f"🔄 复用失败码: {code}") return code if not self.code_list: self.codes_exhausted = True return None code = self.code_list[0] logger.info(f"📄 获取码字: {code} (剩余 {len(self.code_list)-1})") return code def commit_scan_code(self, code: str): """确认码字使用成功,从列表和文件中删除""" if code in self.code_list: self.code_list.remove(code) self.remove_code_from_file(code) def set_pending_code(self, code: str): """将失败码字设置为下次优先使用(不删除)""" self.pending_code = code logger.warning(f"⚠️ 码字失败,顺延下次: {code}") # ==================== 代理、位置、UA ==================== def get_proxy(self) -> Optional[str]: if not self.proxy_url: return None try: resp = requests.get(self.proxy_url, timeout=10) if resp.status_code == 200: return resp.text.strip() except Exception as e: logger.error(f"🌐 获取代理失败: {e}") return None def get_scan_location(self): lat, lon = SCAN_LOCATIONS[self.scan_loc_index % len(SCAN_LOCATIONS)] self.scan_loc_index += 1 return lat, lon def get_ua(self): ua = USER_AGENTS[self.ua_index % len(USER_AGENTS)] self.ua_index += 1 return ua def get_proxies(self, proxy): if proxy: return {"http": f"http://{proxy}", "https": f"http://{proxy}"} return None @staticmethod def is_ssl_error(error_str: str) -> bool: return any(k in error_str.lower() for k in ['ssl', 'eof', 'ssl.c:', 'ssl_eof']) def refresh_proxy_ip(self): self.ssl_error_count = 0 new = self.get_proxy() if new: logger.info(f"🔄 SSL错误,更换IP: {new}") return new return None # ==================== 账号加载 ==================== def load_config(self): self.wx_accounts = self.parse_manual_accounts() def setup_clients(self): 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): accounts = [] text = os.getenv("WLJ_ID", "").strip() if not text: logger.error("❌ 环境变量 WLJ_ID 为空") return accounts for line in 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"✅ 加载账号 {len(accounts)} 个") return accounts # ==================== 签名 ==================== def compute_sign(self, user_code: str) -> str: secret = "cU9(yZ3{zD6!pE4.xX7#" return hashlib.md5((user_code + secret).encode()).hexdigest().upper() # ==================== API调用 ==================== def get_code_from_chicken_farm(self, wxid): try: url = f"{WX_CLOUD}/prod-api/wechat/api/getMiniProgramCode" headers = {"Authorization": AUTH_TOKEN, "Content-Type": "application/json"} payload = {"wxid": wxid, "appid": WX_APPID} resp = requests.post(url, json=payload, headers=headers, timeout=10) data = resp.json() if data.get("code") == 200 and data.get("data", {}).get("code"): return data["data"]["code"] except Exception as e: logger.error(f"❌ 获取code失败: {e}") return None def step1_wlj_login(self, code, proxy): url = f"{WLJ_BASE_URL}/userInfoMini/userMemberLogin" sign = self.compute_sign("") payload = { "code": code, "possessor": POSSESSOR, "userCode": "", "mallCode": MALL_CODE, "saleChannel": SALE_CHANNEL, "sign": sign } headers = {"User-Agent": self.get_ua(), "Content-Type": "application/x-www-form-urlencoded"} try: resp = self.session.post(url, data=urlencode(payload).encode(), headers=headers, proxies=self.get_proxies(proxy), timeout=10) result = resp.json() if result.get("success") and result.get("status") == 1: content = result["content"] if isinstance(content, dict): token = content.get("token") user = content.get("user_summary", {}) return { "token": token, "openid": user.get("openid"), "userCode": user.get("userCode"), "phone": user.get("phone") or user.get("userPhone"), } except Exception as e: logger.error(f"❌ 登录异常: {e}") return None def step2_get_s3_token(self, wlj_token, serial_code, user_code, lat, lon, proxy): url = f"{WLJ_BASE_URL}/openapi/getToken" payload = { "serialCode": serial_code, "latitude": lat, "longitude": lon, "possessor": POSSESSOR, "userCode": user_code, "mallCode": MALL_CODE, "saleChannel": SALE_CHANNEL, } headers = {"Authorization": wlj_token, "User-Agent": self.get_ua()} try: resp = self.session.post(url, data=urlencode(payload).encode(), headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("status") == 1: return data["content"] except Exception as e: logger.error(f"❌ getToken失败: {e}") return None def step3_s3_third_login(self, s3_token, proxy): url = f"{S3_BASE_URL}/openapi/promotion/consumer/auth/thirdLogin" headers = { "Authorization": f"Bearer {s3_token}", "User-Agent": self.get_ua(), "Content-Type": "application/json", } payload = {"appid": WX_APPID, "sign": s3_token} try: resp = requests.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("code") == "200": return data["data"]["token"], False except Exception as e: is_ssl = self.is_ssl_error(str(e)) logger.error(f"❌ thirdLogin异常: {e}") return None, is_ssl return None, False def step4_scan_mask_code(self, s3_token, mask_code, lat, lon, proxy): url = f"{S3_BASE_URL}/openapi/promotion/campaignExecute/scanMaskCode" headers = { "Authorization": f"Bearer {s3_token}", "User-Agent": self.get_ua(), "Content-Type": "application/json", } payload = {"maskCode": mask_code, "lng": lon, "lat": lat, "lastScanResult": False} try: resp = requests.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("code") == "200": biz_msg = data["data"].get("bizMessage", "") if "已超上限" in biz_msg or "超限" in biz_msg: return data["data"], False, True if data["data"].get("bizCode") == "0000000": return data["data"], False, False except Exception as e: logger.error(f"❌ 扫码失败: {e}") is_proxy_err = any(k in str(e) for k in ['Proxy', 'timeout', 'Cannot connect']) return None, is_proxy_err, False return None, False, False def step5_mask_code_lottery(self, s3_token, mask_code, lat, lon, proxy): url = f"{S3_BASE_URL}/openapi/promotion/campaignExecute/maskCodeLottery" headers = { "Authorization": f"Bearer {s3_token}", "User-Agent": self.get_ua(), "Content-Type": "application/json", } payload = {"maskCode": mask_code, "lng": lon, "lat": lat} try: resp = requests.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("code") == "200": return data["data"], False except Exception as e: logger.error(f"❌ 抽奖失败: {e}") is_proxy_err = any(k in str(e) for k in ['Proxy', 'timeout', 'Cannot connect']) return None, is_proxy_err return None, False def step6_receive_reward(self, s3_token, user_reward_id, proxy): url = f"{S3_BASE_URL}/openapi/promotion/consumer/receiveReward" headers = { "Authorization": f"Bearer {s3_token}", "User-Agent": self.get_ua(), "Content-Type": "application/json", } payload = {"userRewardId": user_reward_id} try: resp = requests.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("code") == "200": if data["data"].get("bizCode") == "0000000": return data["data"]["data"], False except Exception as e: is_proxy = any(k in str(e) for k in ['Proxy', 'timeout', 'Cannot connect']) return None, is_proxy return None, False def step7_claim_reward(self, s3_token, user_reward_id, proxy): url = f"{S3_BASE_URL}/openapi/promotion/consumer/claimReward" headers = { "Authorization": f"Bearer {s3_token}", "User-Agent": self.get_ua(), "Content-Type": "application/json", } payload = {"id": user_reward_id} try: resp = requests.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10) data = resp.json() if data.get("success") and data.get("code") == "200": if data["data"].get("bizCode") == "0000000": return data["data"], False except Exception as e: is_proxy = any(k in str(e) for k in ['Proxy', 'timeout', 'Cannot connect']) return None, is_proxy return None, False # ==================== 领取奖励完整流程 ==================== def claim_reward_flow(self, final_s3_token, reward_id, reward_value, current_proxy): """执行步骤6和步骤7,领取并确认奖励""" claim_data, is_claim_err = self.step6_receive_reward(final_s3_token, reward_id, current_proxy) if not claim_data and self.use_proxy and is_claim_err: new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy claim_data, _ = self.step6_receive_reward(final_s3_token, reward_id, current_proxy) if claim_data: _, is_receive_err = self.step7_claim_reward(final_s3_token, reward_id, current_proxy) if _: logger.info(f"💰 到账 {reward_value/100:.2f}元") return reward_value / 100.0, current_proxy return 0.0, current_proxy # ==================== 单次完整扫码抽奖流程 ==================== def do_single_scan_and_lottery(self, final_s3_token, mask_code, scan_lat, scan_lon, current_proxy, seen_reward_ids, hits_count): """ 执行一次完整的扫码+抽奖流程,返回: (lottery_result, new_hits, amount_added, prizes_list, current_proxy) """ # 步骤4 扫码核销 scan_result, is_proxy_err, is_daily_limit = self.step4_scan_mask_code( final_s3_token, mask_code, scan_lat, scan_lon, current_proxy) if is_daily_limit: logger.warning("⚠️ 每日上限") return "daily_limit", hits_count, 0.0, [], current_proxy if not scan_result: if self.use_proxy and is_proxy_err: new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy return None, hits_count, 0.0, [], current_proxy # 步骤5 抽奖 lottery_result, is_lottery_proxy_err = self.step5_mask_code_lottery( final_s3_token, mask_code, scan_lat, scan_lon, current_proxy) if not lottery_result: if self.use_proxy and is_lottery_proxy_err: new_proxy = self.get_proxy() if new_proxy: current_proxy = new_proxy lottery_result, _ = self.step5_mask_code_lottery( final_s3_token, mask_code, scan_lat, scan_lon, current_proxy) if not lottery_result: return None, hits_count, 0.0, [], current_proxy amount_added = 0.0 new_prizes = [] lucky_list = lottery_result.get("luckyRewardList", []) if lucky_list: hits_count += 1 for prize in lucky_list: reward_id = prize.get("userRewardId") # 去重 if reward_id and reward_id in seen_reward_ids: continue if reward_id: seen_reward_ids.add(reward_id) name = prize.get("rewardName", "未知") value = prize.get("rewardValue", 0) reward_type = prize.get("rewardType", "") new_prizes.append(name) logger.info(f"🎁 获得 {name},价值 {value/100:.2f}元") custom = prize.get("customData", {}) if reward_id and reward_type == "redpacket" and custom.get("incentive"): logger.info("💰 红包已激活,领取中...") earned, current_proxy = self.claim_reward_flow(final_s3_token, reward_id, value, current_proxy) amount_added += earned else: logger.info(f"🎟️ 未中奖") return lottery_result, hits_count, amount_added, new_prizes, current_proxy # ==================== 账号处理 ==================== def process_account(self, account): result = { "nickname": account.get("wxName"), "wxid": account.get("wxId"), "phone": account.get("phone"), "attempts": 0, # 实际用码抽奖次数 "hits": 0, # 中奖次数 "prizes": [], # 去重奖品名称 "total_amount": 0.0, "success": False, } wxid = account.get("wxId") account_proxy = None if self.use_proxy: account_proxy = self.get_proxy() if account_proxy: logger.info(f"🌐 代理: {account_proxy}") login_code = self.get_code_from_chicken_farm(wxid) if not login_code: logger.error("❌ 无code,跳过") return result login_result = self.step1_wlj_login(login_code, account_proxy) if not login_result: logger.error("❌ 登录失败,跳过") return result wlj_token = login_result["token"] phone = login_result.get("phone", "") logger.info(f"✅ 登录成功,手机: {phone}") hits = 0 attempts = 0 current_proxy = account_proxy scan_lat, scan_lon = self.get_scan_location() logger.info(f"📍 扫码位置: {scan_lat},{scan_lon}") seen_reward_ids = set() # 用于奖品去重 pending_activation = None # 待激活的奖品信息: {"reward_id": str, "value": int} while hits < self.daily_lottery_limit and attempts < MAX_CODE_ATTEMPTS: # ===== 如果存在待激活的奖品,优先进行激活 ===== if pending_activation: logger.info(f"🔔 有待激活红包,优先激活...") activation_code = self.get_scan_code() if not activation_code: logger.error("❌ 无可用码字进行激活") break # 用新码走一遍步骤2-5进行激活 s3_token = self.step2_get_s3_token(wlj_token, activation_code, login_result["userCode"], scan_lat, scan_lon, current_proxy) if not s3_token: self.set_pending_code(activation_code) break final_s3_token, is_ssl = self.step3_s3_third_login(s3_token, current_proxy) if not final_s3_token: self.set_pending_code(activation_code) break # 扫码+抽奖(用于激活) attempts += 1 scan_result, new_hits, amount_added, new_prizes, current_proxy = self.do_single_scan_and_lottery( final_s3_token, activation_code, scan_lat, scan_lon, current_proxy, seen_reward_ids, hits) # 提交激活码 self.commit_scan_code(activation_code) if scan_result == "daily_limit": break # 检查这次激活的结果 if amount_added > 0: # 激活成功,领取到钱 result["total_amount"] += amount_added # 将奖品名称加入列表(去重已在 do_single_scan_and_lottery 中处理) result["prizes"].extend(new_prizes) pending_activation = None # 激活完成 logger.info(f"✅ 红包激活成功") elif new_hits > hits: # 又中奖了但未激活(incentive=false),更新hits但保持待激活状态 hits = new_hits result["prizes"].extend(new_prizes) # 待激活信息可能已更新,继续激活流程 logger.info(f"🎁 再次中奖,仍待激活") else: # 未中奖,待激活奖品仍在,继续尝试 logger.info(f"🎟️ 激活未成功(未中奖),继续尝试激活") if hits < self.daily_lottery_limit: time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) continue # ===== 正常抽奖流程 ===== logger.info(f"🎯 第 {hits+1}/{self.daily_lottery_limit} 次中奖 (已用码 {attempts})") mask_code = self.get_scan_code() if not mask_code: break # 步骤2 s3_token = self.step2_get_s3_token(wlj_token, mask_code, login_result["userCode"], scan_lat, scan_lon, current_proxy) if not s3_token: self.set_pending_code(mask_code) time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) continue # 步骤3 final_s3_token, is_ssl = self.step3_s3_third_login(s3_token, current_proxy) if is_ssl: self.ssl_error_count += 1 self.set_pending_code(mask_code) if self.ssl_error_count >= 2: new_proxy = self.refresh_proxy_ip() if new_proxy: current_proxy = new_proxy else: break time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) continue if not final_s3_token: self.ssl_error_count = 0 self.set_pending_code(mask_code) time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) continue # 使用码字 attempts += 1 # 执行扫码+抽奖 lottery_result, new_hits, amount_added, new_prizes, current_proxy = self.do_single_scan_and_lottery( final_s3_token, mask_code, scan_lat, scan_lon, current_proxy, seen_reward_ids, hits) # 提交码字 self.commit_scan_code(mask_code) if lottery_result == "daily_limit": break if lottery_result is None: time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) continue # 更新结果 if amount_added > 0: result["total_amount"] += amount_added # 如果金额是在 do_single_scan_and_lottery 中通过激活获得的, # 说明之前有一个待激活的奖品已成功激活 pending_activation = None logger.info(f"✅ 红包已激活并到账") # 检查是否有待激活的奖品 lucky_list = lottery_result.get("luckyRewardList", []) if lottery_result and lottery_result != "daily_limit" else [] has_unactivated = False if lucky_list: for prize in lucky_list: reward_id = prize.get("userRewardId") custom = prize.get("customData", {}) reward_type = prize.get("rewardType", "") if reward_id and reward_type == "redpacket" and not custom.get("incentive"): # 有未激活的红包 pending_activation = { "reward_id": reward_id, "value": prize.get("rewardValue", 0) } has_unactivated = True logger.info(f"🔔 红包待激活: {prize.get('rewardName', '未知')}") break if new_hits > hits: hits = new_hits result["prizes"].extend(new_prizes) if hits < self.daily_lottery_limit and not has_unactivated: time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) result["attempts"] = attempts result["hits"] = hits result["success"] = hits > 0 return result def run(self): if not self.wx_accounts: logger.error("❌ 无账号,退出") return all_results = [] total_attempts = 0 total_amount = 0.0 total_hits = 0 for idx, account in enumerate(self.wx_accounts): nick = account.get('wxName', '未知') logger.info(f"\n==== 账号 {idx+1}/{len(self.wx_accounts)}: {nick} ====") res = self.process_account(account) all_results.append(res) total_attempts += res["attempts"] total_hits += res["hits"] total_amount += res["total_amount"] cost = res["attempts"] * LOTTERY_CODE_PRICE profit = res["total_amount"] - cost logger.info(f"💰 账号{idx+1}: 抽奖{res['attempts']}次 | 中奖{res['hits']}次 | 金额{res['total_amount']:.2f} | 成本{cost:.2f} | 利润{profit:.2f}") if self.codes_exhausted: logger.warning("⚠️ 码字耗尽,停止后续账号") break if idx < len(self.wx_accounts) - 1: time.sleep(self.delay_between_accounts) self.print_summary(all_results, total_attempts, total_hits, total_amount) def print_summary(self, results, total_attempts, total_hits, total_amount): total_cost = total_attempts * LOTTERY_CODE_PRICE total_profit = total_amount - total_cost logger.info("\n" + "="*40) logger.info("📊 执行汇总") logger.info(f"👥 账号: {len(results)} | ✅ 中奖账号: {sum(1 for r in results if r['success'])}") logger.info(f"🎰 总抽奖: {total_attempts} 次 | 🎁 中奖: {total_hits} 次") logger.info(f"💰 总金额: {total_amount:.2f} | 💵 总成本: {total_cost:.2f} | 📈 总利润: {total_profit:.2f}") logger.info("📋 详情:") for i, r in enumerate(results): status = "✅" if r["success"] else "❌" prizes = ", ".join(r["prizes"]) if r["prizes"] else "无" logger.info(f" {status} 账号{i+1}: {r['nickname']} | 抽奖{r['attempts']}次 | 中奖{r['hits']} | 金额{r['total_amount']:.2f} | {prizes}") def main(): automation = WangLaoJiAutomation() automation.run() if __name__ == "__main__": main()