diff --git a/WX_Applet/Applet_WLJi.py b/WX_Applet/Applet_WLJi.py new file mode 100644 index 0000000..2969064 --- /dev/null +++ b/WX_Applet/Applet_WLJi.py @@ -0,0 +1,717 @@ +# 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 +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 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() # 用于奖品去重 + + while hits < self.daily_lottery_limit and attempts < MAX_CODE_ATTEMPTS: + 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 + + # 步骤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("⚠️ 每日上限,停止本账号") + break + if not scan_result: + if self.use_proxy and is_proxy_err: + new_proxy = self.get_proxy() + if new_proxy: + current_proxy = new_proxy + self.set_pending_code(mask_code) + time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max)) + continue + + # 扫码成功,使用码字 + attempts += 1 + + # 步骤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 lottery_result: + lucky_list = lottery_result.get("luckyRewardList", []) + if lucky_list: + hits += 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", "") + result["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("💰 红包已激活,领取中...") + 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 _: + result["total_amount"] += value / 100.0 + logger.info(f"💰 到账 {value/100:.2f}元") + else: + logger.info(f"🎟️ 未中奖") + + # 提交码字使用 + self.commit_scan_code(mask_code) + + if hits < self.daily_lottery_limit: + 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() \ No newline at end of file