Files
Yangmao_Script/WX_Applet/Applet_JDBao.py

675 lines
38 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,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()