添加 WX_Applet/Applet_JDBao.py

This commit is contained in:
2026-05-20 23:04:49 +08:00
parent 1b2188aa24
commit 2c71024103

675
WX_Applet/Applet_JDBao.py Normal file
View File

@@ -0,0 +1,675 @@
# cron: 12 8,12,18 * * *
# new Env("加多宝瓶盖")
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
青龙面板微信协议自动化脚本 - 加多宝扫码抽奖
基于HAR文件分析接入养鸡场自动获取code
支持每个账号轮换扫码位置 + 码字文件管理(失败顺延、成功删除)
修复:识别“已经被核销”等错误,自动删除无效码字,避免死循环
"""
import os
import sys
import json
import time
import io
import requests
import logging
import random
import re
from typing import List, Dict, Optional
# ==================== 养鸡场配置 ====================
WX_CLOUD = os.getenv('wx_cloud', 'http://192.168.31.203:666')
AUTH_TOKEN = os.getenv('wx_token', '')
SCAN_CODE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "JDB_MZ.txt")
# ==================== 代理配置 ====================
PROXY_DEFAULT = ""
USE_PROXY = False
# ==================== 账号配置 ====================
USE_API_ACCOUNTS = False
JDB_ID = os.getenv('JDB_ID', '')
REMOVE_WXIDS = [
"wxid_x4nz2s4th42",
"wxid_fog306o9q22",
"wxid_tso944q0t22",
]
DELAY_BETWEEN_ACCOUNTS = 3
WX_APPID = "wx8371875e443e177f"
# ==================== 抽奖配置 ====================
DAILY_LOTTERY_LIMIT = 1 # 每账号最大成功抽奖次数
MAX_CODE_ATTEMPTS = 2 # 每账号最大使用码次数(包括成功和失败的码)
LOTTERY_INTERVAL_MIN = 5 # 最小抽奖间隔时间(秒)
LOTTERY_INTERVAL_MAX = 15 # 最大抽奖间隔时间(秒)
MAX_RETRY_COUNT = 3 # 最大重试次数
LOTTERY_CODE_PRICE = 0.22 # 有奖码统一价格(元)
# ==================== API配置 ====================
BASE_URL = "https://api-mp.jdbchina.com"
CLIENT_CODE = "CLI2113448692"
PROJECT_CODE = "86000021"
ACT_CODE = "ACT2512151610531"
# ==================== 扫码位置列表(纬度, 经度, 省份, 城市, 区县, 详细地址) ====================
SCAN_LOCATIONS = [
("28.228521", "112.939423", "湖南省", "长沙市", "芙蓉区", "湖南省长沙市芙蓉区五一大道"),
("28.364827", "112.815102", "湖南省", "长沙市", "望城区", "湖南省长沙市望城区雷锋大道"),
("28.157439", "113.632571", "湖南省", "浏阳市", "淮川街道", "湖南省浏阳市金沙中路"),
("28.261334", "112.548920", "湖南省", "宁乡市", "玉潭街道", "湖南省宁乡市花明北路"),
("27.832155", "113.158607", "湖南省", "株洲市", "天元区", "湖南省株洲市天元区天台路"),
("27.672908", "113.497244", "湖南省", "醴陵市", "来龙门街道", "湖南省醴陵市瓷城大道"),
("27.869672", "112.912388", "湖南省", "湘潭市", "雨湖区", "湖南省湘潭市雨湖区建设北路"),
("27.538421", "112.287695", "湖南省", "韶山市", "清溪镇", "湖南省韶山市英雄路"),
("27.751183", "112.503816", "湖南省", "湘乡市", "望春门街道", "湖南省湘乡市东风路"),
("28.358012", "112.196543", "湖南省", "益阳市", "赫山区", "湖南省益阳市赫山区益阳大道"),
("28.498775", "112.221098", "湖南省", "沅江市", "琼湖街道", "湖南省沅江市沅江大道"),
("28.487346", "113.032817", "湖南省", "汨罗市", "归义镇", "湖南省汨罗市建设路"),
("28.683512", "112.869411", "湖南省", "湘阴县", "文星镇", "湖南省湘阴县新世纪大道"),
("27.438907", "111.594236", "湖南省", "娄底市", "娄星区", "湖南省娄底市娄星区乐坪大道"),
]
# ==================== 调试配置 ====================
DEBUG_MODE = False
# ==================== 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",
]
# 自定义日志格式
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 JiaduobaoAutomation:
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.scan_loc_index = 0 # 位置轮换索引
self.pending_code = None # 待重试的码字
self.codes_exhausted = False # 文件是否无码字
# 代理配置
env_proxy = os.getenv('pgdl', '')
if PROXY_DEFAULT:
self.proxy_url = PROXY_DEFAULT
logger.info(f"代理来源: 脚本配置")
elif env_proxy:
self.proxy_url = env_proxy
logger.info(f"代理来源: 青龙变量 pgdl")
else:
self.proxy_url = ""
logger.info(f"未配置代理")
self.use_proxy = USE_PROXY
self.current_proxy = None
self.load_config()
self.setup_clients()
logger.info("加多宝扫码抽奖 - 自动化脚本启动")
logger.info(f"账号数量: {len(self.wx_accounts)}")
logger.info(f"每账号执行: {self.daily_lottery_limit} 次抽奖")
logger.info(f"码字文件: {SCAN_CODE_FILE}")
if self.use_proxy and self.proxy_url:
logger.info(f"代理模式: 开启")
# ==================== 码字文件操作 ====================
def read_codes_from_file(self) -> List[str]:
"""从文件读取所有原始码字(每行一条)"""
try:
if os.path.exists(SCAN_CODE_FILE):
with open(SCAN_CODE_FILE, 'r', encoding='utf-8') as f:
lines = [line.strip() for line in f if line.strip()]
return lines
else:
logger.error(f"码字文件不存在: {SCAN_CODE_FILE}")
return []
except Exception as e:
logger.error(f"读取码字文件失败: {e}")
return []
def remove_used_code(self, code: str) -> bool:
"""从文件中删除指定实际码字对应的原始行"""
try:
raw_lines = self.read_codes_from_file()
# 查找匹配的行原始行可能是URL需提取实际码字比较
target_raw = None
for raw in raw_lines:
if self._extract_code(raw) == code:
target_raw = raw
break
if target_raw:
raw_lines.remove(target_raw)
with open(SCAN_CODE_FILE, 'w', encoding='utf-8') as f:
f.write('\n'.join(raw_lines))
logger.info(f"已从文件删除码字: {code} (剩余: {len(raw_lines)} 个)")
return True
else:
logger.warning(f"码字不在文件中: {code}")
return False
except Exception as e:
logger.error(f"删除码字失败: {e}")
return False
@staticmethod
def _extract_code(raw: str) -> str:
"""从原始行中提取实际码字URL取最后一段否则去空格"""
raw = raw.strip()
if '/' in raw:
return raw.split('/')[-1]
return raw
def get_pending_code(self) -> Optional[str]:
if self.pending_code:
code = self.pending_code
self.pending_code = None
logger.info(f"复用上次失败的码字: {code}")
return code
return None
def set_pending_code(self, code: str):
if code:
logger.warning(f"码字失败,顺延下次使用: {code}")
self.pending_code = code
def commit_scan_code(self, code: str):
"""确认码字已成功核销,从文件删除"""
self.remove_used_code(code)
# ==================== 位置轮换 ====================
def get_scan_location(self) -> tuple:
lat, lon, province, city, area, address = SCAN_LOCATIONS[self.scan_loc_index % len(SCAN_LOCATIONS)]
self.scan_loc_index += 1
return lat, lon, province, city, area, address
# ==================== 代理相关 ====================
def get_proxy(self) -> Optional[str]:
if not self.proxy_url:
return None
try:
response = requests.get(self.proxy_url, timeout=10)
if response.status_code == 200:
try:
data = response.json()
if isinstance(data, dict) and "data" in data:
proxy_list = data["data"]
if isinstance(proxy_list, list) and len(proxy_list) > 0:
ip = proxy_list[0].get("ip")
port = proxy_list[0].get("port")
if ip and port:
proxy = f"{ip}:{port}"
logger.info(f"获取代理成功: {proxy}")
return proxy
except:
pass
proxy = response.text.strip()
if proxy:
logger.info(f"获取代理成功: {proxy}")
return proxy
except Exception as e:
logger.error(f"获取代理失败: {e}")
return None
def get_proxies(self, proxy: Optional[str] = None) -> Optional[dict]:
if proxy:
return {"http": f"http://{proxy}", "https": f"http://{proxy}"}
return None
# ==================== 账号加载 ====================
def load_config(self):
if USE_API_ACCOUNTS:
self.wx_accounts = self.get_wechat_account_list()
else:
self.wx_accounts = self.parse_manual_accounts()
def setup_clients(self):
self.session.headers.update({
"Content-Type": "application/json",
"charset": "utf-8",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://servicewechat.com/wx8371875e443e177f/30/page-frame.html",
})
def get_wechat_account_list(self) -> List[Dict]:
if not AUTH_TOKEN:
logger.error("未设置wx_token")
return []
url = f"{WX_CLOUD}/prod-api/wechat/wechat/list?pageNum=1&pageSize=1000"
headers = {"Authorization": AUTH_TOKEN, "Content-Type": "application/json"}
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
if data.get("code") == 200 and isinstance(data.get("rows"), list):
accounts = [a for a in data["rows"] if a.get("wxId") not in REMOVE_WXIDS]
logger.info(f"养鸡场获取账号成功: {len(accounts)}")
return accounts
except Exception as e:
logger.error(f"获取养鸡场账号失败: {e}")
return []
def parse_manual_accounts(self) -> List[Dict]:
accounts = []
if not JDB_ID:
return accounts
for acc_str in JDB_ID.split():
parts = acc_str.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]
return accounts
# ==================== 养鸡场获取jscode ====================
def get_code_from_chicken_farm(self, wxid: str = None) -> Optional[str]:
try:
url = f"{WX_CLOUD}/prod-api/wechat/api/getMiniProgramCode"
headers = {"Authorization": AUTH_TOKEN, "Content-Type": "application/json"}
payload = {"wxid": wxid or "", "appid": WX_APPID}
resp = requests.post(url, json=payload, headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
if data.get("code") == 200 and data.get("data", {}).get("code"):
code = data["data"]["code"]
logger.info(f"获取jscode成功: {code}")
return code
else:
logger.warning("养鸡场无可用code")
return None
except Exception as e:
logger.error(f"获取code失败: {e}")
return None
# ==================== 扫码码获取(文件+待重试) ====================
def get_scan_code(self) -> Optional[str]:
if self.codes_exhausted:
logger.warning("扫码码已用完,不再获取")
return None
pending = self.get_pending_code()
if pending:
return pending
codes = self.read_codes_from_file()
if codes:
raw = codes[0]
code = self._extract_code(raw)
logger.info(f"从文件获取扫码码: {code} (剩余: {len(codes)-1} 个)")
return code
else:
logger.warning("文件中无码字")
self.codes_exhausted = True
return None
# ==================== API 请求方法 ====================
@staticmethod
def extract_prize_amount(prize_name: str) -> float:
if not prize_name or "积分" in prize_name or "谢谢参与" in prize_name:
return 0.0
match = re.search(r'(\d+(?:\.\d+)?)\s*元', prize_name)
return float(match.group(1)) if match else 0.0
def login(self, jscode: str, proxy: Optional[str] = None) -> Optional[Dict]:
url = f"{BASE_URL}/geement.authjextra/api/v1/loginsession/2weichatmicroprogram"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": random.choice(USER_AGENTS),
"Referer": "https://servicewechat.com/wx8371875e443e177f/30/page-frame.html",
}
data = f"jscode={jscode}&app_id={WX_APPID}&client_code={CLIENT_CODE}"
try:
resp = self.session.post(url, data=data, headers=headers, proxies=self.get_proxies(proxy), timeout=10)
resp.raise_for_status()
result = resp.json()
if result.get("success"):
logger.info(f"登录成功, user_id: {result['data'].get('user_id')}")
return {"token": result["data"]["token"], "user_id": result["data"].get("user_id"), "open_id": result["data"].get("open_id")}
else:
logger.error(f"登录失败: {result.get('msg')}")
return None
except Exception as e:
logger.error(f"登录请求失败: {e}")
return None
def get_user_info(self, token: str, proxy: Optional[str] = None):
url = f"{BASE_URL}/geement.usercenter/api/v1/user/information"
headers = {"apitoken": token, "User-Agent": random.choice(USER_AGENTS)}
try:
resp = self.session.get(url, headers=headers, proxies=self.get_proxies(proxy), timeout=10)
if resp.status_code == 200 and resp.json().get("success"):
info = resp.json()["data"]
logger.info(f"用户: {info.get('nick_name', '')[:16]}, 等级: {info.get('levelinfo', {}).get('level_name', '未知')}")
except Exception as e:
logger.error(f"获取用户信息失败: {e}")
def scan_verify(self, token: str, code_number: str, proxy: Optional[str] = None) -> tuple:
url = f"{BASE_URL}/thirty.jdb/api/lottery/code/outcheck?project_code={PROJECT_CODE}&code={code_number}"
headers = {"apitoken": token, "User-Agent": random.choice(USER_AGENTS)}
try:
resp = self.session.get(url, headers=headers, proxies=self.get_proxies(proxy), timeout=10)
resp.raise_for_status()
result = resp.json()
if result.get("success"):
logger.info(f"扫码验证成功: 箱号={result['data'].get('boxNumber')}")
return result["data"], False
else:
logger.error(f"扫码验证失败: {result.get('msg')}")
return None, False
except Exception as e:
error_str = str(e)
is_proxy_err = any(k in error_str for k in ['Proxy', 'timeout', 'Cannot connect'])
logger.error(f"扫码验证异常: {e}")
return None, is_proxy_err
def luckdraw(self, token: str, code_number: str, lat: str, lon: str, province: str, city: str, area: str, address: str, proxy: Optional[str] = None) -> tuple:
url = f"{BASE_URL}/thirty.jdb/api/lottery"
headers = {
"apitoken": token,
"Content-Type": "application/json-patch+json",
"actcode": ACT_CODE,
"User-Agent": random.choice(USER_AGENTS),
}
payload = {
"code": code_number,
"province_name": province,
"city_name": city,
"area_name": area,
"address": address,
"longitude": float(lon),
"dimension": float(lat),
"project_code": PROJECT_CODE
}
try:
resp = self.session.post(url, json=payload, headers=headers, proxies=self.get_proxies(proxy), timeout=10)
resp.raise_for_status()
result = resp.json()
if result.get("success"):
prize = result["data"].get("lotterydata", {}).get("prizedto", {})
if prize:
name = prize.get("prize_name", "")
amount = self.extract_prize_amount(name)
logger.info(f"抽奖结果: {name}")
return {"prize_name": name, "prize_value": amount}, False, ""
else:
logger.info("抽奖结果: 谢谢参与")
return {"prize_name": "谢谢参与", "prize_value": 0}, False, ""
else:
msg = result.get("msg", "")
if "已经抽过" in msg or "已经扫码" in msg:
logger.warning(f"抽奖失败: {msg} (码已使用)")
elif "抽奖次数已经达到最大" in msg:
logger.warning(f"抽奖失败: {msg} (当前码保留)")
else:
logger.error(f"抽奖失败: {msg}")
return None, False, msg
except Exception as e:
error_str = str(e)
is_proxy_err = any(k in error_str for k in ['Proxy', 'timeout', 'Cannot connect'])
logger.error(f"抽奖请求失败: {e}")
return None, is_proxy_err, error_str
def get_win_goods(self, token: str, proxy: Optional[str] = None) -> Optional[List]:
url = f"{BASE_URL}/geement.actjextra/api/v1/act/win/goods?act_codes={ACT_CODE}"
headers = {"apitoken": token, "User-Agent": random.choice(USER_AGENTS)}
try:
resp = self.session.get(url, headers=headers, proxies=self.get_proxies(proxy), timeout=10)
if resp.status_code == 200 and resp.json().get("success"):
goods = resp.json().get("data", [])
logger.info(f"中奖记录: {len(goods)}")
return goods
except Exception as e:
logger.error(f"查询中奖记录失败: {e}")
return None
# ==================== 单个账号处理 ====================
def process_account(self, account: Dict) -> Dict:
result = {
"nickname": account.get("wxName", ""),
"wxid": account.get("wxId", ""),
"phone": account.get("phone", ""),
"lottery_count": 0,
"code_used_count": 0,
"prizes": [],
"total_amount": 0.0,
"win_records": [],
"success": False,
}
wxid = account.get("wxId", "")
# 每个账号轮换一个位置
lat, lon, province, city, area, address = self.get_scan_location()
logger.info(f"当前账号位置: {province}{city}{area} {address} ({lat},{lon})")
# 代理
account_proxy = None
if self.use_proxy:
account_proxy = self.get_proxy()
if account_proxy:
logger.info(f"使用代理: {account_proxy}")
# 登录
jscode = self.get_code_from_chicken_farm(wxid)
if not jscode:
logger.error("无法获取jscode跳过")
return result
login_result = None
for retry in range(MAX_RETRY_COUNT):
login_result = self.login(jscode, account_proxy)
if login_result:
break
if self.use_proxy and account_proxy:
new_proxy = self.get_proxy()
if new_proxy:
account_proxy = new_proxy
logger.info(f"更换代理: {account_proxy}")
continue
# 如果还有重试机会重新获取jscode
if retry < MAX_RETRY_COUNT - 1 and USE_API_ACCOUNTS:
new_code = self.get_code_from_chicken_farm(wxid)
if new_code:
jscode = new_code
time.sleep(1)
continue
break
if not login_result:
logger.error("登录失败,跳过")
return result
token = login_result["token"]
self.get_user_info(token, account_proxy)
# 抽奖循环
success_cnt = 0
used_cnt = 0
current_proxy = account_proxy
while success_cnt < self.daily_lottery_limit:
if used_cnt >= MAX_CODE_ATTEMPTS:
logger.warning(f"已达最大尝试次数 {MAX_CODE_ATTEMPTS},退出")
break
scan_code = self.get_scan_code()
if not scan_code:
break
logger.info(f"{success_cnt+1}/{self.daily_lottery_limit} 次抽奖 (已用码数: {used_cnt})")
# 扫码验证
scan_res, proxy_err = self.scan_verify(token, scan_code, current_proxy)
if not scan_res:
if self.use_proxy and proxy_err:
new_proxy = self.get_proxy()
if new_proxy:
current_proxy = new_proxy
logger.info(f"更换代理重试扫码")
scan_res, _ = self.scan_verify(token, scan_code, current_proxy)
if not scan_res:
used_cnt += 1
result["code_used_count"] = used_cnt
# 网络类失败,保留码字
self.set_pending_code(scan_code)
time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max))
continue
# 抽奖
lottery_res, lottery_proxy_err, err_msg = self.luckdraw(
token, scan_code, lat, lon, province, city, area, address, current_proxy
)
if not lottery_res and self.use_proxy and lottery_proxy_err:
new_proxy = self.get_proxy()
if new_proxy:
current_proxy = new_proxy
lottery_res, _, err_msg = self.luckdraw(
token, scan_code, lat, lon, province, city, area, address, current_proxy
)
if lottery_res is not None:
# 抽奖成功(包括谢谢参与),码字已被平台核销,删除文件中的该码
self.commit_scan_code(scan_code)
used_cnt += 1
result["code_used_count"] = used_cnt
success_cnt += 1
result["lottery_count"] = success_cnt
prize_val = lottery_res.get("prize_value", 0)
if prize_val > 0:
result["prizes"].append(lottery_res.get("prize_name", ""))
result["total_amount"] += prize_val
else:
# ========== 修复点:增加“已经被核销”等永久性失败的判断 ==========
# 定义需要删除码字的错误关键词(码已失效)
consumed_keywords = ["已经抽过", "已经扫码", "已经被核销", "已核销", "已使用", "串码已经被核销"]
if any(keyword in err_msg for keyword in consumed_keywords):
# 码已被消耗,删除它
self.commit_scan_code(scan_code)
used_cnt += 1
result["code_used_count"] = used_cnt
logger.info(f"码字已失效,已从文件删除: {scan_code}")
elif "抽奖次数已经达到最大" in err_msg:
# 账号达上限,码字未消耗,保留给下一个账号
self.set_pending_code(scan_code)
logger.warning("当前账号已达上限,切换账号")
break
else:
# 其他失败(如网络、代理错误),保留码字重试
self.set_pending_code(scan_code)
used_cnt += 1
result["code_used_count"] = used_cnt
if success_cnt < self.daily_lottery_limit:
time.sleep(random.uniform(self.lottery_interval_min, self.lottery_interval_max))
result["success"] = result["lottery_count"] > 0
# 查询中奖记录
if result["lottery_count"] > 0:
win = self.get_win_goods(token, account_proxy)
if win:
result["win_records"] = win
for g in win:
status = "已发放" if g.get("grant_status") == 30 else "未发放"
logger.info(f"中奖: {g.get('win_prize_name')} | {status}")
return result
# ==================== 主流程 ====================
def run(self):
if not self.wx_accounts:
logger.error("没有可用账号")
return
all_results = []
total_lottery = 0
total_amount = 0.0
success_count = 0
for idx, acc in enumerate(self.wx_accounts):
nickname = acc.get('wxName', '未知')
logger.info(f"\n========== 账号 {idx+1}/{len(self.wx_accounts)} {nickname} ==========")
res = self.process_account(acc)
all_results.append(res)
total_lottery += res["lottery_count"]
total_amount += res["total_amount"]
if res["success"]:
success_count += 1
cost = res.get("code_used_count", res["lottery_count"]) * LOTTERY_CODE_PRICE
profit = res["total_amount"] - cost
logger.info(f"账号 {idx+1} 成功抽奖: {res['lottery_count']}次, 用码: {res.get('code_used_count',0)}个, 金额: {res['total_amount']:.2f}元, 成本: {cost:.2f}元, 利润: {profit:.2f}")
if self.codes_exhausted:
logger.warning("码字文件已无码,停止后续账号")
break
if idx < len(self.wx_accounts) - 1:
logger.info(f"等待 {self.delay_between_accounts} 秒...")
time.sleep(self.delay_between_accounts)
# 汇总
total_used = sum(r.get("code_used_count", 0) for r in all_results)
logger.info("\n" + "="*50)
logger.info("执行汇总")
logger.info(f"总账号数: {len(all_results)} 成功: {success_count}")
logger.info(f"总成功抽奖: {total_lottery}")
logger.info(f"总用码: {total_used}")
logger.info(f"总金额: {total_amount:.2f}")
logger.info(f"总成本: {total_used * LOTTERY_CODE_PRICE:.2f}")
logger.info(f"总利润: {total_amount - total_used * LOTTERY_CODE_PRICE:.2f}")
for i, r in enumerate(all_results):
status = "" if r["success"] else ""
logger.info(f" {status} 账号{i+1}: {r['nickname']} | 抽奖: {r['lottery_count']}次 | 金额: {r['total_amount']:.2f}")
def main():
JiaduobaoAutomation().run()
if __name__ == "__main__":
main()