Files
Yangmao_Script/WX_Applet/Applet_WLJi.py
2026-05-20 15:02:51 +08:00

833 lines
42 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cron: 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()