更新 WX_Applet/Applet_JYHS_DDYX.py

This commit is contained in:
2026-05-24 02:27:18 +08:00
parent 5834aaca48
commit 16b15b71f7

View File

@@ -1,469 +1,604 @@
# cron: 2 7 * * * # cron: 2 7 * * *
# new Env("旧衣回收_铛铛一下") # new Env("旧衣回收_铛铛一下_通用版")
import hashlib """
import random 旧衣回收_铛铛一下 通用一体化版本
import time
import requests 核心链路:
import os wx_cloud + wx_token + wxid + appid
import logging -> 养鸡场 getMiniProgramCode
import traceback -> https://vues.dd1x.cn/wechat/login?code=xxx&channelId=154
import base64 -> data.token
-> 后续接口使用 Header: Token
外部环境变量尽量保持最少:
1. wx_cloud 养鸡场地址,默认 http://192.168.31.203:666
2. wx_token 养鸡场 Authorization默认自动补 Bearer
3. DDYX_TEST_WXID 可选,只跑指定 wxid便于测试
4. DDYX_EXCLUDE_WXIDS 可选,排除 wxid支持换行/逗号/&/@ 分隔
缓存:
同目录 APP_Buffer/ddyx_token_cache.json
日志:
同目录 logs/ddyx_YYYYMMDD_HHMMSS.log
"""
import json import json
from Crypto.Cipher import AES import logging
from Crypto.Random import get_random_bytes import os
import random
import re
import sys
import time
import traceback
from datetime import datetime from datetime import datetime
from pathlib import Path
# ====================== 养鸡场配置 ====================== import requests
WX_TOKEN = os.getenv('wx_token') # 养鸡场 Authorization 令牌
wx_cloud = os.getenv('wx_cloud', 'http://192.168.31.203:666') # 养鸡场服务地址
# ========================================================
remove_wxids1 = ["wxid_11111111111111"] # 需要剔除的多个wxid # ====================== 项目固定配置 ======================
DEFAULT_WITHDRAW_BALANCE = 0.3 # 默认超过该金额进行提现需大于等于0.3 PROJECT_NAME = "旧衣回收_铛铛一下"
MULTI_ACCOUNT_SPLIT = ["\n", "@"] # 分隔符列表 APPID = "wxe378d2d7636c180e"
MULTI_ACCOUNT_PROXY = False # 是否使用多账号代理默认不使用True则使用多账号代理 HOST = "vues.dd1x.cn"
BASE_URL = f"https://{HOST}"
CHANNEL_ID = 154
LOTTERY_ACTIVITY_ID = 3438615
# 微信小程序ID根据实际业务调整 # 默认超过该金额可提现;是否自动提现见 ENABLE_WITHDRAW
WX_APPID = "wxe378d2d7636c180e" WITHDRAW_THRESHOLD = 0.3
ENABLE_WITHDRAW = True # 原脚本默认自动提现;如需关闭改成 False
class WXBizDataCryptUtil: # Token 缓存有效期,默认 7 天
""" TOKEN_CACHE_TTL = 7 * 24 * 3600
微信小程序加解密工具
"""
def __init__(self, sessionKey):
self.sessionKey = sessionKey
def encrypt(self, data, iv=None): # 多账号间隔,防止请求过快
""" ACCOUNT_DELAY_RANGE = (2, 5)
data: dict或str若为dict自动转为json字符串 REQUEST_DELAY_RANGE = (1, 3)
iv: base64字符串若为None自动生成 LOTTERY_DELAY_RANGE = (3, 5)
返回: (加密数据base64, iv base64)
"""
if isinstance(data, dict):
data = json.dumps(data, separators=(',', ':'))
if iv is None:
iv_bytes = get_random_bytes(16)
iv = base64.b64encode(iv_bytes).decode('utf-8')
else:
iv_bytes = base64.b64decode(iv)
sessionKey = base64.b64decode(self.sessionKey)
cipher = AES.new(sessionKey, AES.MODE_CBC, iv_bytes)
padded = self._pad(data.encode('utf-8'))
encrypted = cipher.encrypt(padded)
encrypted_b64 = base64.b64encode(encrypted).decode('utf-8')
return encrypted_b64, iv
def decrypt(self, encryptedData, iv): # 养鸡场分页:每页 1 个账号,真正做到取一个跑一个
""" CLOUD_PAGE_SIZE = 1
encryptedData: base64字符串
iv: base64字符串
返回: dict或str
"""
sessionKey = base64.b64decode(self.sessionKey)
encryptedData = base64.b64decode(encryptedData)
iv = base64.b64decode(iv)
cipher = AES.new(sessionKey, AES.MODE_CBC, iv)
decrypted = self._unpad(cipher.decrypt(encryptedData))
try:
return json.loads(decrypted)
except Exception:
return decrypted.decode('utf-8')
def _pad(self, s): # 调试开关;普通模式不打印请求/响应详情、不打印完整 token/code
pad_len = 16 - len(s) % 16 DEBUG = False
return s + bytes([pad_len] * pad_len) AUTO_BEARER = True
def _unpad(self, s): # 代理开关
return s[:-s[-1]] MULTI_ACCOUNT_PROXY = False
PROXY_API_URL = os.getenv("PROXY_API_URL", "").strip()
class AutoTask: DEFAULT_USER_AGENT = (
def __init__(self, site_name): "Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.220303.001; wv) "
""" "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
初始化自动任务类 "Chrome/134.0.6998.136 Mobile Safari/537.36 XWEB/1340129 "
:param site_name: 站点名称,用于日志显示 "MMWEBSDK/20240301 MMWEBID/9871 MicroMessenger/8.0.48.2580(0x28003036) "
""" "WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android"
self.site_name = site_name
self.proxy_url = os.getenv("PROXY_API_URL") # 代理api返回一条txt文本内容为代理ip:端口
self.wx_appid = WX_APPID # 微信小程序id
self.host = "vues.dd1x.cn"
self.user_agent = "Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.220303.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.136 Mobile Safari/537.36 XWEB/1340129 MMWEBSDK/20240301 MMWEBID/9871 MicroMessenger/8.0.48.2580(0x28003036) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android"
self.setup_logging()
def setup_logging(self):
"""
配置日志系统
"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s\t- %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.StreamHandler()
]
) )
def get_proxy(self): AUTH_FAIL_KEYWORDS = [
""" "未登录", "请登录", "登录失效", "登录过期", "token", "Token", "TOKEN",
获取代理 "无效", "过期", "未授权", "unauthorized", "Unauthorized", "401", "403"
:return: 代理 ]
"""
if not self.proxy_url:
logging.warning("[获取代理]没有找到环境变量PROXY_API_URL不使用代理")
return None
url = self.proxy_url
response = requests.get(url)
proxy = response.text.strip() # 去除首尾空格和换行
logging.info(f"[获取代理]: {proxy}")
return proxy
def check_proxy(self, proxy, session): # ====================== 路径配置 ======================
""" BASE_DIR = Path(__file__).resolve().parent
检查代理 BUFFER_DIR = BASE_DIR / "APP_Buffer"
:param proxy: 代理 LOG_DIR = BASE_DIR / "logs"
:param session: session BUFFER_DIR.mkdir(parents=True, exist_ok=True)
:return: 是否可用 LOG_DIR.mkdir(parents=True, exist_ok=True)
"""
try: TOKEN_CACHE_FILE = BUFFER_DIR / "ddyx_token_cache.json"
url = f"http://{self.host}/api/v2/get_sign_list" LOG_FILE = LOG_DIR / f"ddyx_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
session.headers["Token"] = ""
response = session.get(url, timeout=5) # ====================== 环境变量 ======================
if response.status_code == 200: wx_cloud = os.getenv("wx_cloud", "http://192.168.31.203:666").strip().rstrip("/")
logging.info(f"[检查代理]: {proxy} 应该可用") _raw_wx_token = os.getenv("wx_token", "").strip()
return True if AUTO_BEARER and _raw_wx_token and not _raw_wx_token.lower().startswith("bearer "):
WX_TOKEN = f"Bearer {_raw_wx_token}"
else: else:
logging.info(f"[检查代理]: {response.text}") WX_TOKEN = _raw_wx_token
return False
except Exception as e:
logging.error(f"[检查代理]代理 {proxy} 不可用: {str(e)}")
return False
def get_wechat_account_list(self): TEST_WXID = os.getenv("DDYX_TEST_WXID", "").strip()
""" EXCLUDE_WXIDS_RAW = os.getenv("DDYX_EXCLUDE_WXIDS", "").strip()
从养鸡场接口获取账号列表
:return: 账号列表/False # 原脚本内置排除;保留但集中放这里
""" SCRIPT_EXCLUDE_WXIDS = ["wxid_11111111111111"]
def setup_logging():
logging.basicConfig(
level=logging.DEBUG if DEBUG else logging.INFO,
format="%(asctime)s - %(levelname)s\t- %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler(LOG_FILE, encoding="utf-8"),
],
)
def split_multi_values(raw):
"""支持换行、逗号、中文逗号、&、@ 分隔。"""
if not raw:
return []
parts = re.split(r"[\n,&@]+", raw)
return [x.strip() for x in parts if x.strip()]
def mask_secret(value, keep_start=6, keep_end=4):
if value is None:
return ""
value = str(value)
if len(value) <= keep_start + keep_end:
return "***"
return f"{value[:keep_start]}***{value[-keep_end:]}"
def mask_phone(tel):
if not tel:
return ""
tel = str(tel)
if len(tel) >= 7:
return tel[:3] + "****" + tel[-4:]
return "***"
def load_json_file(path, default):
if not path.exists():
return default
try: try:
if not WX_TOKEN: return json.loads(path.read_text(encoding="utf-8"))
logging.error("[获取账号列表]未设置环境变量 wx_token") except Exception as e:
return False logging.warning(f"[缓存]读取失败,使用空缓存: {path} | {e}")
return default
url = f"{wx_cloud}/prod-api/wechat/wechat/list?pageNum=1&pageSize=1000"
headers = { def save_json_file(path, data):
'Authorization': WX_TOKEN, try:
'Content-Type': 'application/json', path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
except Exception as e:
logging.warning(f"[缓存]保存失败: {path} | {e}")
class DDYXTask:
def __init__(self):
self.token_cache = load_json_file(TOKEN_CACHE_FILE, {})
self.exclude_wxids = set(SCRIPT_EXCLUDE_WXIDS + split_multi_values(EXCLUDE_WXIDS_RAW))
self.total_accounts = 0
self.success_accounts = 0
self.login_success_accounts = 0
self.sign_success_accounts = 0
self.draw_success_count = 0
self.withdraw_success_count = 0
self.account_reports = []
# ---------------------- 养鸡场相关 ----------------------
def yjc_headers(self):
return {
"Authorization": WX_TOKEN,
"Content-Type": "application/json",
} }
response = requests.get(url, headers=headers, timeout=10) def iter_cloud_accounts(self):
response.raise_for_status() """分页流式获取账号:取一个账号,跑完,再取下一个。"""
res_data = response.json() page_num = 1
while True:
if res_data and res_data.get('code') == 200 and isinstance(res_data.get('rows'), list): url = f"{wx_cloud}/prod-api/wechat/wechat/list"
accounts = res_data['rows'] params = {"pageNum": page_num, "pageSize": CLOUD_PAGE_SIZE}
logging.info(f"[获取账号列表]成功获取到 {len(accounts)} 个账号")
return accounts
else:
logging.error(f"[获取账号列表]接口返回异常: {json.dumps(res_data)}")
return False
except Exception as e:
logging.error(f"[获取账号列表]发生错误: {str(e)}\n{traceback.format_exc()}")
return False
def get_wx_code_yjc(self, wxid):
"""
使用养鸡场接口获取微信小程序Code
:param wxid: 微信ID
:return: code/False
"""
try: try:
if not WX_TOKEN: res = requests.get(url, params=params, headers=self.yjc_headers(), timeout=15)
logging.error("[获取Code]未设置环境变量 wx_token") res.raise_for_status()
return False data = res.json()
except Exception as e:
logging.error(f"[养鸡场]获取账号列表失败 page={page_num}: {e}")
if DEBUG:
logging.debug(traceback.format_exc())
return
if data.get("code") != 200 or not isinstance(data.get("rows"), list):
logging.error(f"[养鸡场]账号列表返回异常: {json.dumps(data, ensure_ascii=False)[:500]}")
return
rows = data.get("rows", [])
if not rows:
return
for row in rows:
wxid = row.get("wxId") or row.get("wxid") or ""
wxname = row.get("wxName") or row.get("remark") or row.get("nickName") or "未知昵称"
if not wxid:
logging.warning("[养鸡场]账号缺少 wxId跳过")
continue
yield {"wxid": wxid, "wxname": wxname}
total = data.get("total")
if total is not None and page_num * CLOUD_PAGE_SIZE >= int(total):
return
if len(rows) < CLOUD_PAGE_SIZE:
return
page_num += 1
def get_wx_code(self, wxid):
url = f"{wx_cloud}/prod-api/wechat/api/getMiniProgramCode" url = f"{wx_cloud}/prod-api/wechat/api/getMiniProgramCode"
headers = { payload = {"wxid": wxid, "appid": APPID}
'Authorization': WX_TOKEN, try:
'Content-Type': 'application/json' res = requests.post(url, json=payload, headers=self.yjc_headers(), timeout=15)
} res.raise_for_status()
payload = { data = res.json()
"wxid": wxid, code = data.get("data", {}).get("code")
"appid": self.wx_appid if data.get("code") == 200 and code:
} logging.info("[获取code]完成")
if DEBUG:
response = requests.post(url, json=payload, headers=headers, timeout=10) logging.debug(f"[获取code][debug] {mask_secret(code)}")
response.raise_for_status()
res_data = response.json()
if res_data and res_data.get('code') == 200 and res_data.get('data', {}).get('code'):
code = res_data['data']['code']
logging.info(f"[获取Code]成功获取 wxid:{wxid} 的code")
return code return code
else: logging.warning(f"[获取code]失败: {json.dumps(data, ensure_ascii=False)[:500]}")
logging.error(f"[获取Code]接口返回异常: {json.dumps(res_data)}") return None
return False
except Exception as e: except Exception as e:
logging.error(f"[获取Code]wxid:{wxid} 发生错误: {str(e)}\n{traceback.format_exc()}") logging.error(f"[获取code]异常: {e}")
if DEBUG:
logging.debug(traceback.format_exc())
return None
# ---------------------- 请求会话 ----------------------
def build_session(self):
session = requests.Session()
session.headers.update({"User-Agent": DEFAULT_USER_AGENT})
if MULTI_ACCOUNT_PROXY and PROXY_API_URL:
proxy = self.get_proxy()
if proxy:
session.proxies.update({"http": f"http://{proxy}", "https": f"http://{proxy}"})
logging.info("[代理]已启用")
return session
def get_proxy(self):
try:
res = requests.get(PROXY_API_URL, timeout=10)
proxy = res.text.strip()
if DEBUG:
logging.debug(f"[代理]获取到: {proxy}")
return proxy
except Exception as e:
logging.warning(f"[代理]获取失败,不使用代理: {e}")
return None
def is_auth_failed_response(self, data):
try:
text = json.dumps(data, ensure_ascii=False)
except Exception:
text = str(data)
return any(k in text for k in AUTH_FAIL_KEYWORDS)
def validate_token(self, session):
"""用查询余额/提现列表接口做缓存 Token 校验,不写入业务。"""
url = f"{BASE_URL}/api/h/get_withdrawal_trade_list"
try:
res = session.get(url, timeout=15)
if res.status_code in (401, 403):
return False
data = res.json()
if self.is_auth_failed_response(data):
return False
return True
except Exception as e:
if DEBUG:
logging.debug(f"[Token校验]异常: {e}")
return False return False
def wxlogin(self, session, code): def login_by_code(self, session, code):
""" url = f"{BASE_URL}/wechat/login"
登录 params = {"code": code, "channelId": CHANNEL_ID}
:param session: session
:param code: 微信code
:return: 登录结果
"""
try: try:
url = f"https://{self.host}/wechat/login" res = session.get(url, params=params, timeout=15)
params = { res.raise_for_status()
"code": code, data = res.json()
"channelId": 154 if DEBUG:
} safe_data = json.loads(json.dumps(data, ensure_ascii=False))
response = session.get(url, params=params, timeout=10) if isinstance(safe_data.get("data"), dict) and safe_data["data"].get("token"):
response.raise_for_status() safe_data["data"]["token"] = mask_secret(safe_data["data"]["token"])
response_json = response.json() logging.debug(f"[登录响应] {json.dumps(safe_data, ensure_ascii=False)[:1000]}")
if response_json.get('code') == 0: if data.get("code") == 0 and isinstance(data.get("data"), dict) and data["data"].get("token"):
tel = response_json['data'].get('tel', '') token = data["data"]["token"]
# 号码中间4位*号代替 tel = data["data"].get("tel", "")
if tel:
tel = tel[:3] + "****" + tel[-4:]
logging.info(f"[登录]成功: 当前账号 {tel}")
token = response_json['data']['token']
session.headers["Token"] = token session.headers["Token"] = token
return True logging.info(f"[登录]完成 | 手机: {mask_phone(tel)}")
else: return {
logging.error(f"[登录]发生错误: {response_json.get('msg', '未知错误')}") "token": token,
return False "tel": mask_phone(tel),
except requests.RequestException as e: "rawTel": tel,
logging.error(f"[登录]发生网络错误: {str(e)}\n{traceback.format_exc()}") "updatedAt": int(time.time()),
return False }
logging.warning(f"[登录]失败: {data.get('msg', '未知错误')}")
return None
except Exception as e: except Exception as e:
logging.error(f"[登录]发生错误: {str(e)}\n{traceback.format_exc()}") logging.error(f"[登录]异常: {e}")
if DEBUG:
logging.debug(traceback.format_exc())
return None
def prepare_token(self, session, wxid, wxname):
now = int(time.time())
cached = self.token_cache.get(wxid, {})
cached_token = cached.get("token")
updated_at = int(cached.get("updatedAt", 0) or 0)
if cached_token and now - updated_at < TOKEN_CACHE_TTL:
session.headers["Token"] = cached_token
logging.info(f"[Token缓存]命中 | {cached.get('tel') or wxname},开始校验")
if self.validate_token(session):
logging.info("[Token缓存]校验通过")
return True
logging.info("[Token缓存]校验失败,重新登录")
elif cached_token:
logging.info("[Token缓存]已过期,重新登录")
else:
logging.info("[Token缓存]未命中,开始登录")
code = self.get_wx_code(wxid)
if not code:
return False return False
login_info = self.login_by_code(session, code)
if not login_info:
return False
self.token_cache[wxid] = {"wxName": wxname, **login_info}
save_json_file(TOKEN_CACHE_FILE, self.token_cache)
logging.info("[Token缓存]已更新")
return True
# ---------------------- 业务接口 ----------------------
def sign_in(self, session): def sign_in(self, session):
""" url = f"{BASE_URL}/api/v2/sign_join"
签到
:param session: session
:return: 签到结果
"""
try: try:
url = f"https://{self.host}/api/v2/sign_join" res = session.get(url, timeout=15)
response = session.get(url, timeout=10) res.raise_for_status()
response.raise_for_status() data = res.json()
response_json = response.json() if data.get("code") == 0:
logging.info("[签到]完成 | 成功")
if response_json.get('code') == 0: return {"ok": True, "msg": "成功"}
logging.info(f"[签到]: 成功") msg = data.get("msg", "未知错误")
return True logging.info(f"[签到]完成 | {msg}")
else: return {"ok": False, "msg": msg}
logging.warning(f"[签到]: {response_json.get('msg', '未知错误')}")
return False
except Exception as e: except Exception as e:
logging.error(f"[签到]发生错误: {str(e)}\n{traceback.format_exc()}") logging.warning(f"[签到]异常: {e}")
return False return {"ok": False, "msg": str(e)}
def draw_once(self, session):
url = f"{BASE_URL}/front/activity/update_lottery_result"
params = {"id": LOTTERY_ACTIVITY_ID}
try:
res = session.get(url, params=params, timeout=15)
res.raise_for_status()
data = res.json()
if data.get("code") == 0:
prize = (data.get("data") or {}).get("goodName", "未知奖励")
logging.info(f"[抽奖]完成 | 获得: {prize}")
return {"ok": True, "prize": prize, "msg": "成功"}
msg = data.get("msg", "未知错误")
logging.info(f"[抽奖]完成 | {msg}")
return {"ok": False, "prize": "", "msg": msg}
except Exception as e:
logging.warning(f"[抽奖]异常: {e}")
return {"ok": False, "prize": "", "msg": str(e)}
def add_lottery_count(self, session): def add_lottery_count(self, session):
""" url = f"{BASE_URL}/front/activity/add_lottery_count"
增加抽奖次数
:param session: session
:return: 增加抽奖次数结果
"""
try: try:
url = f"https://{self.host}/front/activity/add_lottery_count" res = session.get(url, timeout=15)
response = session.get(url, timeout=10) res.raise_for_status()
response.raise_for_status() data = res.json()
response_json = response.json() if data.get("code") == 0:
logging.info("[增加抽奖次数]完成 | 成功")
if response_json.get('code') == 0: return {"ok": True, "msg": "成功"}
logging.info(f"[增加抽奖次数]: 成功") msg = data.get("msg", "未知错误")
return True logging.info(f"[增加抽奖次数]完成 | {msg}")
elif "达到上限" in response_json.get('msg', ''): return {"ok": False, "msg": msg}
logging.warning(f"[增加抽奖次数]: {response_json.get('msg')}")
return False
else:
logging.error(f"[增加抽奖次数]发生错误: {response_json.get('msg', '未知错误')}")
return False
except Exception as e: except Exception as e:
logging.error(f"[增加抽奖次数]发生错误: {str(e)}\n{traceback.format_exc()}") logging.warning(f"[增加抽奖次数]异常: {e}")
return False return {"ok": False, "msg": str(e)}
def update_lottery_result(self, session): def run_lottery_flow(self, session):
""" prizes = []
更新抽奖结果(执行抽奖)
:param session: session
:return: 抽奖结果
"""
try:
url = f"https://{self.host}/front/activity/update_lottery_result"
params = {
"id": 3438615
}
response = session.get(url, params=params, timeout=10)
response.raise_for_status()
response_json = response.json()
if response_json.get('code') == 0: # 先直接抽,直到服务器提示不能继续
good_name = response_json['data'].get('goodName', '未知奖励') while True:
logging.info(f"[抽奖]: 获得{good_name}") result = self.draw_once(session)
return True if not result["ok"]:
else: break
logging.warning(f"[抽奖]: {response_json.get('msg', '未知错误')}") prizes.append(result.get("prize", "未知奖励"))
return False self.draw_success_count += 1
except Exception as e: time.sleep(random.randint(*LOTTERY_DELAY_RANGE))
logging.error(f"[抽奖]发生错误: {str(e)}\n{traceback.format_exc()}")
return False # 尝试增加次数,每次增加成功后抽一次
while True:
add_result = self.add_lottery_count(session)
if not add_result["ok"]:
break
time.sleep(random.randint(*REQUEST_DELAY_RANGE))
draw_result = self.draw_once(session)
if draw_result["ok"]:
prizes.append(draw_result.get("prize", "未知奖励"))
self.draw_success_count += 1
time.sleep(random.randint(*LOTTERY_DELAY_RANGE))
return prizes
def get_withdrawal_trade_list(self, session): def get_withdrawal_trade_list(self, session):
""" url = f"{BASE_URL}/api/h/get_withdrawal_trade_list"
获取提现相关数据
:param session: session
:return: (余额, 提现数据)/False
"""
try: try:
url = f"https://{self.host}/api/h/get_withdrawal_trade_list" res = session.get(url, timeout=15)
response = session.get(url, timeout=10) res.raise_for_status()
response.raise_for_status() data = res.json()
response_json = response.json() if data.get("code") == 0 and isinstance(data.get("data"), list) and data["data"]:
balance = float(data["data"][0].get("money", 0) or 0)
if response_json.get('code') == 0 and isinstance(response_json.get('data'), list) and len(response_json['data']) > 0: logging.info(f"[余额查询]完成 | 余额: {balance}")
balance = float(response_json['data'][0].get('money', 0)) return {"ok": True, "balance": balance, "list": data["data"], "msg": "成功"}
logging.info(f"[余额]: {balance}") msg = data.get("msg", "未知错误")
return balance, response_json['data'] logging.info(f"[余额查询]完成 | {msg}")
else: return {"ok": False, "balance": 0.0, "list": [], "msg": msg}
logging.error(f"[获取提现相关数据]发生错误: {response_json.get('msg', '未知错误')}")
return False
except Exception as e: except Exception as e:
logging.error(f"[获取提现相关数据]发生错误: {str(e)}\n{traceback.format_exc()}") logging.warning(f"[余额查询]异常: {e}")
return False return {"ok": False, "balance": 0.0, "list": [], "msg": str(e)}
def withdraw(self, session, balance, withdrawal_trade_list): def withdraw(self, session, balance, withdrawal_list):
""" url = f"{BASE_URL}/api/h/withdrawal"
提现
:param session: session
:param balance: 余额
:param withdrawal_trade_list: 提现相关数据
:return: 提现结果
"""
try:
url = f"https://{self.host}/api/h/withdrawal"
payload = { payload = {
"totalMoney": balance, "totalMoney": balance,
"type": 1, "type": 1,
"withdrawalDetailPojoList": withdrawal_trade_list "withdrawalDetailPojoList": withdrawal_list,
} }
response = session.post(url, json=payload, timeout=10)
response.raise_for_status()
response_json = response.json()
if response_json.get('code') == 0:
logging.info(f"[提现]: {response_json.get('msg', '提现成功')}")
return True
else:
logging.warning(f"[提现]: {response_json.get('msg', '提现失败')}")
return False
except Exception as e:
logging.error(f"[提现]发生错误: {str(e)}\n{traceback.format_exc()}")
return False
def run(self):
"""
运行任务
"""
try: try:
logging.info(f"{self.site_name}】开始执行任务") res = session.post(url, json=payload, timeout=15)
res.raise_for_status()
data = res.json()
if data.get("code") == 0:
msg = data.get("msg", "提现成功")
logging.info(f"[提现]完成 | {msg}")
self.withdraw_success_count += 1
return {"ok": True, "msg": msg}
msg = data.get("msg", "提现失败")
logging.info(f"[提现]完成 | {msg}")
return {"ok": False, "msg": msg}
except Exception as e:
logging.warning(f"[提现]异常: {e}")
return {"ok": False, "msg": str(e)}
# 从养鸡场获取账号列表 # ---------------------- 单账号执行 ----------------------
accounts = self.get_wechat_account_list() def should_skip_account(self, wxid):
if not accounts: if TEST_WXID and wxid != TEST_WXID:
logging.error("{self.site_name}】获取账号列表失败,任务终止") return "非测试账号"
return if wxid in self.exclude_wxids:
return "排除列表"
return ""
# 过滤需要剔除的账号 def run_one_account(self, index, wxid, wxname):
combined_remove = set(remove_wxids1) report = {
filtered_accounts = [acc for acc in accounts if acc.get('wxId') not in combined_remove] "index": index,
logging.info(f"[账号过滤]剔除后剩余 {len(filtered_accounts)} 个账号") "wxid": wxid,
"wxname": wxname,
# 执行每个账号的任务 "login": False,
for index, account in enumerate(filtered_accounts, 1): "sign": "未执行",
wxid = account.get('wxId') "draw_count": 0,
wxname = account.get('wxName', '未知昵称') "prizes": [],
"balance": 0.0,
"withdraw": "未执行",
"status": "失败",
}
logging.info("") logging.info("")
logging.info(f"------ 【账号{index}: {wxname} ({wxid})】开始执行任务 ------") logging.info(f"========== 账号{index} | {wxname} | {wxid} 开始 ==========")
# 初始化session和代理 skip_reason = self.should_skip_account(wxid)
if MULTI_ACCOUNT_PROXY: if skip_reason:
proxy = self.get_proxy() report["status"] = f"跳过:{skip_reason}"
session = requests.Session() logging.info(f"[账号跳过]原因: {skip_reason}")
if proxy: self.account_reports.append(report)
session.proxies.update({ return
"http": f"http://{proxy}",
"https": f"http://{proxy}"
})
# 检查代理,不可用则不使用代理
if not self.check_proxy(proxy, session):
session.proxies.clear()
logging.warning("[代理]使用默认网络环境")
else:
session = requests.Session()
session.headers["User-Agent"] = self.user_agent session = self.build_session()
# 执行微信授权获取code if not self.prepare_token(session, wxid, wxname):
code = self.get_wx_code_yjc(wxid) report["status"] = "登录失败"
if code: logging.info(f"========== 账号{index} | 完成 | 登录失败 ==========")
# 登录 self.account_reports.append(report)
login_result = self.wxlogin(session, code) return
time.sleep(random.randint(1, 3))
if login_result: report["login"] = True
# 签到 self.login_success_accounts += 1
self.sign_in(session) time.sleep(random.randint(*REQUEST_DELAY_RANGE))
time.sleep(random.randint(1, 3))
# 抽奖循环 sign_result = self.sign_in(session)
lottery_result = self.update_lottery_result(session) report["sign"] = sign_result.get("msg", "未知")
while lottery_result: if sign_result.get("ok"):
time.sleep(random.randint(3, 5)) self.sign_success_accounts += 1
lottery_result = self.update_lottery_result(session) time.sleep(random.randint(*REQUEST_DELAY_RANGE))
# 增加抽奖次数并继续抽奖 prizes = self.run_lottery_flow(session)
add_count_result = self.add_lottery_count(session) report["prizes"] = prizes
while add_count_result: report["draw_count"] = len(prizes)
self.update_lottery_result(session)
time.sleep(random.randint(3, 5))
add_count_result = self.add_lottery_count(session)
# 提现逻辑
withdraw_data = self.get_withdrawal_trade_list(session) withdraw_data = self.get_withdrawal_trade_list(session)
if withdraw_data: if withdraw_data.get("ok"):
balance, withdrawal_list = withdraw_data balance = withdraw_data.get("balance", 0.0)
if balance >= DEFAULT_WITHDRAW_BALANCE: report["balance"] = balance
self.withdraw(session, balance, withdrawal_list) if ENABLE_WITHDRAW:
time.sleep(random.randint(1, 3)) if balance >= WITHDRAW_THRESHOLD:
withdraw_result = self.withdraw(session, balance, withdraw_data.get("list", []))
report["withdraw"] = withdraw_result.get("msg", "未知")
else: else:
logging.warning(f"[提现]: 余额{balance}元不足{DEFAULT_WITHDRAW_BALANCE}元,不进行提现") report["withdraw"] = f"余额不足{WITHDRAW_THRESHOLD}元,不提现"
logging.info(f"[提现]完成 | 余额{balance}元不足{WITHDRAW_THRESHOLD}元,不提现")
else: else:
logging.warning("[提现]: 获取提现数据失败") report["withdraw"] = "未开启自动提现"
logging.info("[提现]完成 | 未开启自动提现")
else: else:
logging.error(f"[授权]wxid:{wxid} 获取Code失败跳过该账号") report["withdraw"] = f"余额查询失败: {withdraw_data.get('msg', '')}"
logging.info(f"------ 【账号{index}: {wxname} ({wxid})】执行任务完成 ------") report["status"] = "完成"
# 账号间增加延迟,避免请求过快 self.success_accounts += 1
if index < len(filtered_accounts): self.account_reports.append(report)
time.sleep(random.randint(2, 5)) logging.info(
f"========== 账号{index} | 完成 | 签到:{report['sign']} | "
f"抽奖:{report['draw_count']}次 | 余额:{report['balance']}元 | 提现:{report['withdraw']} =========="
)
except Exception as e: # ---------------------- 主入口 ----------------------
logging.error(f"{self.site_name}】执行过程中发生错误: {str(e)}\n{traceback.format_exc()}") def run(self):
finally: logging.info(f"{PROJECT_NAME}】开始执行")
logging.info(f"{self.site_name}】所有账号任务执行完毕") logging.info(f"[配置] APPID: {APPID}")
logging.info(f"[配置] 养鸡场: {wx_cloud}")
logging.info(f"[配置] Token缓存: {TOKEN_CACHE_FILE}")
logging.info(f"[配置] 日志文件: {LOG_FILE}")
logging.info(f"[配置] 自动提现: {'开启' if ENABLE_WITHDRAW else '关闭'} | 阈值: {WITHDRAW_THRESHOLD}")
if not WX_TOKEN:
logging.error("[启动失败]请先设置环境变量 wx_token")
return
index = 0
for account in self.iter_cloud_accounts():
index += 1
self.total_accounts += 1
self.run_one_account(index, account["wxid"], account["wxname"])
time.sleep(random.randint(*ACCOUNT_DELAY_RANGE))
save_json_file(TOKEN_CACHE_FILE, self.token_cache)
self.print_summary()
def print_summary(self):
logging.info("")
logging.info("==================== 执行汇总 ====================")
logging.info(f"项目: {PROJECT_NAME}")
logging.info(f"总账号数: {self.total_accounts}")
logging.info(f"登录成功账号数: {self.login_success_accounts}")
logging.info(f"任务完成账号数: {self.success_accounts}")
logging.info(f"签到成功账号数: {self.sign_success_accounts}")
logging.info(f"成功抽奖次数: {self.draw_success_count}")
logging.info(f"提现成功次数: {self.withdraw_success_count}")
logging.info(f"Token缓存文件: {TOKEN_CACHE_FILE}")
logging.info(f"日志文件: {LOG_FILE}")
logging.info("-------------------- 账号明细 --------------------")
for r in self.account_reports:
prize_text = "".join(r.get("prizes") or []) or ""
logging.info(
f"账号{r['index']} | {r['wxname']} | 状态:{r['status']} | "
f"签到:{r['sign']} | 抽奖:{r['draw_count']}次 | 奖品:{prize_text} | "
f"余额:{r['balance']}元 | 提现:{r['withdraw']}"
)
logging.info("==================================================")
if __name__ == "__main__": if __name__ == "__main__":
# 检查必要环境变量 setup_logging()
if not WX_TOKEN: try:
print("错误:请先设置环境变量 wx_token") DDYXTask().run()
exit(1) except KeyboardInterrupt:
logging.warning("用户中断执行")
except Exception as e:
logging.error(f"主程序异常: {e}")
if DEBUG:
logging.debug(traceback.format_exc())
auto_task = AutoTask("铛铛一下")
auto_task.run()