# 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()