From 2c71024103db49f8c0b10cb031a475453a9a35bc Mon Sep 17 00:00:00 2001 From: admin <362324317@qq.com> Date: Wed, 20 May 2026 23:04:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20WX=5FApplet/Applet=5FJDBao?= =?UTF-8?q?.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WX_Applet/Applet_JDBao.py | 675 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 WX_Applet/Applet_JDBao.py diff --git a/WX_Applet/Applet_JDBao.py b/WX_Applet/Applet_JDBao.py new file mode 100644 index 0000000..e1e3849 --- /dev/null +++ b/WX_Applet/Applet_JDBao.py @@ -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() \ No newline at end of file