diff --git a/电信金豆话费并发本002.py b/电信金豆话费并发本002.py new file mode 100644 index 0000000..641d6df --- /dev/null +++ b/电信金豆话费并发本002.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf002="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费002.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费002.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费002.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf002')!= None: + chinaTelecomAccount = os.environ.get('jdhf002') + else: + chinaTelecomAccount = jdhf002 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf002 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本003.py b/电信金豆话费并发本003.py new file mode 100644 index 0000000..e395fcd --- /dev/null +++ b/电信金豆话费并发本003.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf003="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费003.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费003.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费003.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf003')!= None: + chinaTelecomAccount = os.environ.get('jdhf003') + else: + chinaTelecomAccount = jdhf003 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf003 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本004.py b/电信金豆话费并发本004.py new file mode 100644 index 0000000..19c0a76 --- /dev/null +++ b/电信金豆话费并发本004.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf004="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费004.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费004.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费004.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf004')!= None: + chinaTelecomAccount = os.environ.get('jdhf004') + else: + chinaTelecomAccount = jdhf004 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf004 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本005.py b/电信金豆话费并发本005.py new file mode 100644 index 0000000..b7de3c6 --- /dev/null +++ b/电信金豆话费并发本005.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf005="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费005.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费005.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费005.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf005')!= None: + chinaTelecomAccount = os.environ.get('jdhf005') + else: + chinaTelecomAccount = jdhf005 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf005 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本006.py b/电信金豆话费并发本006.py new file mode 100644 index 0000000..59b05ba --- /dev/null +++ b/电信金豆话费并发本006.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf006="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费006.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费006.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费006.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf006')!= None: + chinaTelecomAccount = os.environ.get('jdhf006') + else: + chinaTelecomAccount = jdhf006 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf006 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本007.py b/电信金豆话费并发本007.py new file mode 100644 index 0000000..9be83e5 --- /dev/null +++ b/电信金豆话费并发本007.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf007="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费007.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费007.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费007.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf007')!= None: + chinaTelecomAccount = os.environ.get('jdhf007') + else: + chinaTelecomAccount = jdhf007 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf007 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/电信金豆话费并发本008.py b/电信金豆话费并发本008.py new file mode 100644 index 0000000..a961fb4 --- /dev/null +++ b/电信金豆话费并发本008.py @@ -0,0 +1,973 @@ +# !/usr/bin/python3 +# -- coding: utf-8 -- +# ------------------------------- +#电信兑话费 +#export jdhf008="phone#服务密码&phone2#服务密码" +# cron:55 59 9,13 * * * +# const $ = new Env('电信金豆换话费') +import requests +import re +import time +import json +import random +import datetime +import base64 +import threading +import ssl +import execjs +import os +import sys +import urllib3 +import concurrent.futures +import ntplib +import subprocess +import io +import codecs +from operator import itemgetter + +from bs4 import BeautifulSoup + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import DES3 +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor +from Crypto.Cipher import AES +from http import cookiejar # Python 2: import cookielib as cookiejar +from requests.adapters import HTTPAdapter +from urllib3.util.ssl_ import create_urllib3_context + + + + +# 标准输出重定向 +original_stdout = sys.stdout +original_stderr = sys.stderr + +# 创建UTF-8编码的输出流 +utf8_stdout = codecs.getwriter('utf-8')(original_stdout.buffer, 'replace') +utf8_stderr = codecs.getwriter('utf-8')(original_stderr.buffer, 'replace') + +# 替换标准输出流 +sys.stdout = utf8_stdout +sys.stderr = utf8_stderr + +# 初始化通知服务 +send = None # 初始化 send 变量 +if os.path.isfile('notify.py'): + try: + from notify import send + print("加载通知服务成功!") + except Exception as e: + print(f"加载通知服务异常: {str(e)}") +else: + print("未找到notify.py,将使用内置通知方式") + + +class BlockAll(cookiejar.CookiePolicy): + return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False + netscape = True + rfc2965 = hide_cookie2 = False + +# 添加日志控制变量 +VERBOSE_LOG = False # 设置为True时输出详细日志,False时只输出重要日志 + +def printn(m, important=False): + # 如果是重要日志或者详细日志模式开启,才输出 + if important or VERBOSE_LOG: + print(f'\n{m}') + +ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1') + +ip_list = [] +class DESAdapter(HTTPAdapter): + def __init__(self, *args, **kwargs): + """ + A TransportAdapter that re-enables 3DES support in Requests. + """ + CIPHERS = ORIGIN_CIPHERS.split(':') + random.shuffle(CIPHERS) + CIPHERS = ':'.join(CIPHERS) + self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5' + super().__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + context = create_urllib3_context(ciphers=self.CIPHERS) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + kwargs['ssl_context'] = context + return super(DESAdapter, self).proxy_manager_for(*args, **kwargs) + + +requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +ssl_context.set_ciphers('DEFAULT@SECLEVEL=0') +ss = requests.session() +ss.verify = False +ss.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} +ss.mount('https://', DESAdapter()) +yc = 0.1 +wt = 0 +kswt = 0 +yf = datetime.datetime.now().strftime("%Y%m") + +# 添加测试模式变量 +is_test_mode = False + +# 添加服务器时间偏移量变量 +server_time_offset = 0 + +# 修改jp字典,增加10点和14点的键 +jp = {"9":{},"10":{},"12":{},"13":{},"14":{},"23":{}} + +# 添加全局商品信息缓存 +all_goods_cache = {} +preloaded_accounts = set() + +try: + with open('电信金豆换话费008.log') as fr: + dhjl = json.load(fr) +except: + dhjl = {} +if yf not in dhjl: + dhjl[yf] = {} + +# 添加线程锁,防止并发写入冲突 +dhjl_lock = threading.Lock() +goods_cache_lock = threading.Lock() + +# 增加账号会话缓存 +account_sessions = {} +account_sessions_lock = threading.Lock() + +wxp={} +errcode = { + "0":"兑换成功", + "412":"兑换次数已达上限", + "413":"商品已兑完", + "420":"未知错误", + "410":"该活动已失效~", + "Y0001":"当前等级不足,去升级兑当前话费", + "Y0002":"使用翼相连网络600分钟或连接并拓展网络500分钟可兑换此奖品", + "Y0003":"使用翼相连共享流量400M或共享WIFI:2GB可兑换此奖品", + "Y0004":"使用翼相连共享流量2GB可兑换此奖品", + "Y0005":"当前等级不足,去升级兑当前话费", + "E0001":"您的网龄不足10年,暂不能兑换" +} + +#加密参数 +key = b'1234567`90koiuyhgtfrdews' +iv = 8 * b'\0' + +public_key_b64 = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB +-----END PUBLIC KEY-----''' + +public_key_data = '''-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB +-----END PUBLIC KEY-----''' + +# 添加NTP服务器列表 +ntp_servers = [ + 'ntp.myhuaweicloud.com', + 'ntp.aliyun.com', + 'ntp.tencent.com', + 'time.windows.com', + 'time.apple.com', + 'pool.ntp.org' +] + +# 获取服务器时间并计算偏移量 +def sync_time(): + global server_time_offset + + for server in ntp_servers: + try: + printn(f"正在与NTP服务器 {server} 同步时间...", important=True) + client = ntplib.NTPClient() + response = client.request(server, timeout=5) + server_time = response.tx_time + local_time = time.time() + server_time_offset = server_time - local_time + + printn(f"时间同步成功! 服务器时间偏移量: {server_time_offset:.2f}秒", important=True) + return True + except Exception as e: + printn(f"与NTP服务器 {server} 同步时间失败: {str(e)}") + + printn("所有NTP服务器同步失败,将使用本地时间", important=True) + return False + +# 获取当前准确时间(考虑服务器偏移) +def get_accurate_time(): + return time.time() + server_time_offset + +# 修改t函数,支持测试模式和服务器时间偏移 +def t(h): + # 测试模式下返回当前时间戳减去5秒,使脚本立即开始执行 + if is_test_mode: + return get_accurate_time() - 5 + + date = datetime.datetime.now() + date_zero = datetime.datetime.now().replace(year=date.year, month=date.month, day=date.day, hour=h, minute=59, second=59) + date_zero_time = int(time.mktime(date_zero.timetuple())) + + # 应用服务器时间偏移 + return date_zero_time + server_time_offset + + +def encrypt(text): + cipher = DES3.new(key, DES3.MODE_CBC, iv) + ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size)) + return ciphertext.hex() + +def decrypt(text): + ciphertext = bytes.fromhex(text) + cipher = DES3.new(key, DES3.MODE_CBC, iv) + plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size) + return plaintext.decode() + + +def b64(plaintext): + public_key = RSA.import_key(public_key_b64) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return base64.b64encode(ciphertext).decode() + +def encrypt_para(plaintext): + public_key = RSA.import_key(public_key_data) + cipher = PKCS1_v1_5.new(public_key) + ciphertext = cipher.encrypt(plaintext.encode()) + return ciphertext.hex() + + +def encode_phone(text): + encoded_chars = [] + for char in text: + encoded_chars.append(chr(ord(char) + 2)) + return ''.join(encoded_chars) + +def ophone(t): + key = b'34d7cb0bcdf07523' + utf8_key = key.decode('utf-8') + utf8_t = t.encode('utf-8') + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size)) + return ciphertext.hex() + +# 发送微信推送通知 +def send(uid, content): + if not appToken or not uid: + printn("未设置appToken或uid,无法发送通知") + return False + + try: + r = requests.post('https://wxpusher.zjiecode.com/api/send/message', + json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}).json() + printn(f"通知发送结果: {r}", important=True) + return r.get('success', False) + except Exception as e: + printn(f"发送通知失败: {str(e)}", important=True) + return False + +# 使用系统通知脚本发送通知 +def send_system_notify(title, content): + try: + # 获取当前脚本所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + notify_script = os.path.join(current_dir, 'notify.py') + + # 检查通知脚本是否存在 + if not os.path.exists(notify_script): + printn(f"通知脚本不存在: {notify_script}") + return False + + # 调用通知脚本 + cmd = [sys.executable, notify_script, title, content] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + printn(f"系统通知发送成功", important=True) + return True + else: + printn(f"系统通知发送失败: {result.stderr}", important=True) + return False + except Exception as e: + printn(f"发送系统通知时出错: {str(e)}", important=True) + return False + +def userLoginNormal(phone,password): + alphabet = 'abcdef0123456789' + uuid = [''.join(random.sample(alphabet, 8)),''.join(random.sample(alphabet, 4)),'4'+''.join(random.sample(alphabet, 3)),''.join(random.sample(alphabet, 4)),''.join(random.sample(alphabet, 12))] + timestamp=datetime.datetime.now().strftime("%Y%m%d%H%M%S") + loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.' + + try: + r = ss.post('https://appgologin.189.cn:9031/login/client/userLoginNormal',json={"headerInfos": {"code": "userLoginNormal", "timestamp": timestamp, "broadAccount": "", "broadToken": "", "clientType": "#9.6.1#channel50#iPhone 14 Pro Max#", "shopId": "20002", "source": "110003", "sourcePassword": "Sid98s", "token": "", "userLoginName": phone}, "content": {"attach": "test", "fieldData": {"loginType": "4", "accountType": "", "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric), "deviceUid": uuid[0] + uuid[1] + uuid[2], "phoneNum": encode_phone(phone), "isChinatelecom": "0", "systemVersion": "15.4.0", "authentication": password}}}).json() + + if VERBOSE_LOG: + printn(f"登录响应: {r}") # 只在详细日志模式下打印完整响应 + + if 'responseData' not in r or 'data' not in r['responseData'] or 'loginSuccessResult' not in r['responseData']['data']: + printn(f"登录失败: 响应数据结构不正确", important=True) + return False + + l = r['responseData']['data']['loginSuccessResult'] + + if l: + load_token[phone] = l + with open(load_token_file, 'w') as f: + json.dump(load_token, f) + ticket = get_ticket(phone,l['userId'],l['token']) + return ticket + except Exception as e: + printn(f"登录过程中发生错误: {str(e)}", important=True) + + return False + +def get_ticket(phone,userId,token): + r = ss.post('https://appgologin.189.cn:9031/map/clientXML',data='getSingle'+datetime.datetime.now().strftime("%Y%m%d%H%M%S")+'#9.6.1#channel50#iPhone 14 Pro Max#20002110003Sid98s'+token+''+phone+'test'+encrypt(userId)+'4a6862274835b451',headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}) + + #printn(phone, '获取ticket', re.findall('(.*?)',r.text)[0]) + + tk = re.findall('(.*?)',r.text) + if len(tk) == 0: + return False + + return decrypt(tk[0]) + +def queryInfo(phone,s): + global rs + a = 1 + while a < 10: + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + r = s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo',cookies=ck).json() + + try: + printn(f'{phone} 金豆余额 {r["biz"]["amountTotal"]}', important=True) + amountTotal= r["biz"]["amountTotal"] + except: + amountTotal = 0 + if amountTotal< 3000: + if rs == 1: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + res = s.post('http://wapact.189.cn:9000/gateway/stand/detail/exchange',json={"activityId":jdaid},cookies=ck).text + + if '$_ts=window' in res: + first_request() + rs = 1 + + time.sleep(3) + else: + return r + a += 1 + + return r + +# 预加载商品信息的函数 +def preload_goods(phone, s): + """预加载商品信息,提前准备好抢购数据""" + global all_goods_cache + + # 检查是否已经有其他账号预加载过商品信息 + if all_goods_cache and len(all_goods_cache) > 0: + # 使用第一个账号的缓存数据 + first_account = list(all_goods_cache.keys())[0] + with goods_cache_lock: + all_goods_cache[phone] = all_goods_cache[first_account] + preloaded_accounts.add(phone) + + printn(f"{phone} 使用已缓存的商品信息,跳过请求") + return all_goods_cache[phone] + + try: + printn(f"{phone} 正在预加载商品信息...") + if rs: + bd = js.call('main').split('=') + ck[bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck, timeout=30).json() + + # 创建一个列表来存储所有话费商品 + goods_list = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + goods_list.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + goods_list.sort(key=lambda x: x["face_value"], reverse=True) + + # 使用线程锁保护共享资源 + with goods_cache_lock: + all_goods_cache[phone] = goods_list + preloaded_accounts.add(phone) + + printn(f"{phone} 预加载商品信息完成,共 {len(goods_list)} 个话费商品", important=True) + return goods_list + except Exception as e: + printn(f"{phone} 预加载商品信息失败: {str(e)}", important=True) + return [] + +# 优化的exchange函数,使用线程安全的方式更新记录 +def exchange(phone, s, title, aid, uid, session_id): + try: + # 复制一份cookies,避免多线程冲突 + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + # 添加会话ID,便于追踪请求 + headers = { + "X-Session-ID": session_id, + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + # 增加超时时间,从5秒改为15秒 + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=15) # 增加超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0: + if r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} {errcode[r["biz"]["resultCode"]]} [会话:{session_id}]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功 [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送通知成功", important=True) + except Exception as e: + printn(f"通过notify.py发送通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"兑换成功通知已发送: {msg}", important=True) + + # 使用线程锁保护共享资源 + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费008.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + else: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求失败: {r.get("message", "未知错误")} [会话:{session_id}]') + + except requests.exceptions.Timeout: + # 专门处理超时异常 + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 请求超时,正在重试... [会话:{session_id}]') + # 超时后自动重试一次 + try: + local_ck = ck.copy() + if rs: + bd = js.call('main').split('=') + local_ck[bd[0]] = bd[1] + + headers = { + "X-Session-ID": f"{session_id}-retry", + "Connection": "keep-alive", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36", + "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html" + } + + r = s.post('https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange', + json={"activityId": aid}, + cookies=local_ck, + headers=headers, + timeout=20) # 重试时使用更长的超时时间 + + if '$_ts=window' in r.text: + first_request(r.text) + return + + r = r.json() + + if r["code"] == 0 and r["biz"] != {} and r["biz"]["resultCode"] in errcode: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试结果: {errcode[r["biz"]["resultCode"]]} [会话:{session_id}-retry]', important=True) + + if r["biz"]["resultCode"] in ["0", "412"]: + if r["biz"]["resultCode"] == "0": + # 重试兑换成功,发送通知 + notify_title = f"电信金豆换话费 - {phone[-4:]}" + msg = f"{phone}:{title}兑换成功(重试) [时间:{str(datetime.datetime.now())[11:22]}]" + + # 使用notify.py中的send函数发送通知 + if 'send' in globals() and send is not None: + try: + send(notify_title, msg) + printn(f"通过notify.py发送重试成功通知", important=True) + except Exception as e: + printn(f"通过notify.py发送重试通知失败: {str(e)}", important=True) + # 如果notify.py发送失败,尝试使用微信推送 + send_result = send(uid, msg) + # 尝试使用系统通知 + send_system_notify(notify_title, msg) + else: + # 如果notify.py未加载成功,使用备用通知方式 + send_result = send(uid, msg) + send_system_notify(notify_title, msg) + + printn(f"重试兑换成功通知已发送: {msg}", important=True) + + with dhjl_lock: + if phone not in dhjl[yf][title]: + dhjl[yf][title] += "#"+phone + with open('电信金豆换话费008.log', 'w') as f: + json.dump(dhjl, f, ensure_ascii=False) + except Exception as retry_e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 重试也失败: {str(retry_e)} [会话:{session_id}-retry]', important=True) + + except Exception as e: + printn(f'{str(datetime.datetime.now())[11:22]} {phone} {title} 异常: {str(e)} [会话:{session_id}]', important=True) + +# 优化的dh函数,使用线程池提高并发效率 +def dh(phone, s, title, aid, wt, uid): + # 测试模式下跳过等待 + if is_test_mode: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 测试模式:跳过等待") + else: + # 计算精确的等待时间 + current_time = get_accurate_time() + wait_time = wt - current_time + + if wait_time > 0: + # 如果等待时间大于5秒,先粗略等待到还剩5秒 + if wait_time > 5: + rough_wait = wait_time - 5 + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 等待 {rough_wait:.2f} 秒后开始精确倒计时") + time.sleep(rough_wait) + + # 精确倒计时最后5秒 + start_time = time.time() + while time.time() < start_time + min(wait_time, 5): + remaining = wt - get_accurate_time() + if remaining <= 0: + break + + if remaining < 5: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 倒计时: {int(remaining)}秒") + # 使用更短的睡眠时间提高精度 + time.sleep(0.1) + + # 记录开始兑换的精确时间 + exchange_start_time = datetime.datetime.now() + printn(f"{str(exchange_start_time)[11:23]} {phone} {title} 开始兑换") + + # 创建线程池,提高并发效率 + with concurrent.futures.ThreadPoolExecutor(max_workers=cfcs) as executor: + futures = [] + for i in range(cfcs): + # 为每个请求创建唯一的会话ID + session_id = f"{phone[-4:]}-{i+1}-{int(time.time()*1000)%10000}" + # 提交任务到线程池,使用更短的延迟 + future = executor.submit(exchange, phone, s, title, aid, uid, session_id) + futures.append(future) + + # 等待所有任务完成,但最多等待8秒 + done, not_done = concurrent.futures.wait(futures, timeout=8) + + if not_done: + printn(f"{str(datetime.datetime.now())[11:22]} {phone} {title} 有 {len(not_done)} 个请求超时未完成") + + # 记录结束时间并计算总耗时 + exchange_end_time = datetime.datetime.now() + total_time = (exchange_end_time - exchange_start_time).total_seconds() + printn(f"{str(exchange_end_time)[11:23]} {phone} {title} 抢购任务已全部提交,耗时 {total_time:.3f} 秒") + +def lottery(s): + for cishu in range(3): + try: + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + else: + cookie = {} + r = s.post('https://wapact.189.cn:9001/gateway/golden/api/lottery',json={"activityId":"6384b49b1e44396da4f1e4a3"},cookies=ck) + except: + pass + time.sleep(3) + +def aes_ecb_encrypt(plaintext, key): + key = key.encode('utf-8') + if len(key) not in [16, 24, 32]: + raise ValueError("密钥长度必须为16/24/32字节") + + # 对明文进行PKCS7填充 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + # 创建AES ECB加密器 + cipher = AES.new(key, AES.MODE_ECB) + + # 加密并返回Base64编码结果 + ciphertext = cipher.encrypt(padded_data) + return base64.b64encode(ciphertext).decode('utf-8') + +# 解析话费面值,用于排序 +def parse_face_value(title): + # 提取数字部分 + match = re.search(r'(\d+(?:\.\d+)?)', title) + if match: + return float(match.group(1)) + return 0 + +# 优化的ks函数,支持测试模式和10点、14点抢购,并按面值大到小排序 +def ks(phone, ticket, uid): + global wt + + wxp[phone] = uid + # 创建一个新的会话,避免多用户间的冲突 + s = requests.session() + s.verify = False + s.headers={"User-Agent":"Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36","Referer":"https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"} + s.cookies.set_policy(BlockAll()) + s.mount('https://', DESAdapter()) + s.timeout = 60 # 增加默认超时时间到60秒 + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + data = aes_ecb_encrypt(json.dumps({"ticket":ticket,"backUrl":"https%3A%2F%2Fwapact.189.cn%3A9001","platformCode":"P201010301","loginType":2}), 'telecom_wap_2018') + + login = ss.post('https://wapact.189.cn:9001/unified/user/login',data=data, headers={"Content-Type":"application/json;charset=UTF-8","Accept":"application/json, text/javascript, */*; q=0.01"}, cookies=ck).json() + + if login['code'] == 0: + printn(phone+" 获取token成功") + s.headers["Authorization"] = "Bearer " + login["biz"]["token"] + queryInfo(phone,s) + + if rs: + bd = js.call('main').split('=') + ck [bd[0]] = bd[1] + + # 获取商品列表 + queryBigDataAppGetOrInfo = s.get('https://waphub.189.cn/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page&1&order=3&tabOrder=',cookies=ck).json() + print(queryBigDataAppGetOrInfo) + # 创建一个列表来存储所有话费商品,以便后续排序 + all_goods = [] + + for i in queryBigDataAppGetOrInfo["biz"]["ExchangeGoodslist"]: + if '话费' not in i["title"]: + continue + + # 将所有话费商品添加到列表中 + all_goods.append({ + "title": i["title"], + "id": i["id"], + "face_value": parse_face_value(i["title"]) + }) + + # 同时也按原来的逻辑分类到不同时间段 + if '0.5元' in i["title"] or '5元' in i["title"]: + jp["10"][i["title"]] = i["id"] + jp["9"][i["title"]] = i["id"] # 兼容原有逻辑 + elif '1元' in i["title"] or '10元' in i["title"]: + jp["14"][i["title"]] = i["id"] + jp["13"][i["title"]] = i["id"] # 兼容原有逻辑 + else: + jp["12"][i["title"]] = i["id"] + + # 按面值从大到小排序 + all_goods.sort(key=lambda x: x["face_value"], reverse=True) + + # 输出所有可兑换的话费商品 + printn("所有可兑换的话费商品(按面值从大到小排序):") + for idx, good in enumerate(all_goods): + printn(f"{idx+1}. {good['title']} (ID: {good['id']})") + + h = datetime.datetime.now().hour + if 11 > h > 1: + h = 9 + elif 23 > h > 1: + h = 13 + else: + h = 23 + + # 命令行参数处理 + if len(sys.argv) == 2: + if sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + else: + h = int(sys.argv[1]) + printn(f"使用命令行指定的时间: {h}:00") + + # 根据时间选择对应的商品 + if h == 10: + d = jp["10"] + printn(f"将在10:00进行抢购") + elif h == 14: + d = jp["14"] + printn(f"将在14:00进行抢购") + else: + d = jp[str(h)] + + # 如果没有特定时间段的商品,使用所有话费商品 + if not d: + printn(f"当前时间段 {h}:00 没有指定商品,将抢购所有话费商品") + d = {good["title"]: good["id"] for good in all_goods} + + # 与服务器同步时间 + sync_time() + + wt = t(h) + kswt + + # 测试模式下立即执行 + if is_test_mode: + wt = int(get_accurate_time()) - 5 + printn("测试模式:立即开始抢购") + + if jp["12"] != {}: + d.update(jp["12"]) + if len(d) == len(jp["12"]): + wt = 0 + + # 按面值从大到小排序商品 + sorted_goods = [] + for title, aid in d.items(): + face_value = 0 + for good in all_goods: + if good["title"] == title: + face_value = good["face_value"] + break + sorted_goods.append({"title": title, "id": aid, "face_value": face_value}) + + sorted_goods.sort(key=lambda x: x["face_value"], reverse=True) + + printn(f"将按以下顺序抢购商品(面值从大到小):") + for idx, good in enumerate(sorted_goods): + printn(f"{idx+1}. {good['title']} (面值: {good['face_value']})") + + # 创建线程池处理多个商品的抢购 + with concurrent.futures.ThreadPoolExecutor(max_workers=len(sorted_goods)) as executor: + futures = [] + for good in sorted_goods: + title = good["title"] + aid = good["id"] + + if title not in dhjl[yf]: + dhjl[yf][title] = "" + if phone in dhjl[yf][title]: + printn(f"{phone} {title} 已兑换") + else: + printn(f"{phone} {title}") + if wt - get_accurate_time() > 20 * 60 and not is_test_mode: + print("等待时间超过20分钟") + return + + # 提交抢购任务到线程池 + future = executor.submit(dh, phone, s, title, aid, wt, uid) + futures.append(future) + + # 等待所有抢购任务完成 + for future in concurrent.futures.as_completed(futures): + try: + future.result() + except Exception as e: + printn(f"抢购任务异常: {str(e)}") + else: + printn(f"{phone} 获取token {login['message']}") + + +def first_request(res=''): + global js, fw + url = 'https://wapact.189.cn:9001/gateway/stand/detail/exchange' + if res == '': + response = ss.get(url) + res = response.text + soup = BeautifulSoup(res, 'html.parser') + scripts = soup.find_all('script') + for script in scripts: + if 'src' in str(script): + rsurl = re.findall('src="([^"]+)"', str(script))[0] + + if '$_ts=window' in script.get_text(): + ts_code = script.get_text() + + urls = url.split('/') + rsurl = urls[0] + '//' + urls[2] + rsurl + #print(rsurl) + ts_code += ss.get(rsurl).text + content_code = soup.find_all('meta')[1].get('content') + + # 修改此部分 - 添加安全的文件读取方式 + js_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "瑞数通杀.js") + js_code_ym = "" + + # 尝试多种编码方式读取文件 + encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'latin-1'] + for encoding in encodings: + try: + with open(js_file_path, 'r', encoding=encoding) as f: + js_code_ym = f.read() + print(f"成功使用 {encoding} 编码读取JS文件") + break + except UnicodeDecodeError: + continue + except Exception as e: + print(f"读取JS文件时发生错误: {str(e)}") + break + + if not js_code_ym: + print("警告: 无法读取瑞数JS文件,脚本可能无法正常工作") + return False + + # 安全替换和编译JS代码 + try: + js_code = js_code_ym.replace('content_code', content_code).replace("'ts_code'", ts_code) + js = execjs.compile(js_code) + except Exception as e: + print(f"编译JS代码时发生错误: {str(e)}") + return False + + for cookie in ss.cookies: + ck[cookie.name] = cookie.value + return content_code, ts_code, ck + +# 修改main函数,支持测试模式和服务器时间同步 +def main(): + global wt, rs, appToken + + # 检查是否有测试模式参数 + if len(sys.argv) > 1 and sys.argv[1] == '--test': + global is_test_mode + is_test_mode = True + printn("测试模式已启用,将跳过等待时间直接执行") + + # 与服务器同步时间 + sync_time() + + # 设置微信推送的appToken + appToken = os.environ.get('appToken') if os.environ.get('appToken') else "" + + r = ss.get('https://wapact.189.cn:9001/gateway/stand/detailNew/exchange') + if '$_ts=window' in r.text: + rs = 1 + print("0.5元话费,每月限领1次,日上限200份,100金豆,\n1元话费,每月限领1次,日上限200份,200金豆\n5元话费,每月限领1次,日上限180份,1000金豆\n10元话费,每月限领1次,日上限140份,2000金豆") + print("瑞数加密已开启") + first_request() + else: + print("瑞数加密已关闭") + rs = 0 + if os.environ.get('jdhf008')!= None: + chinaTelecomAccount = os.environ.get('jdhf008') + else: + chinaTelecomAccount = jdhf008 + + # 增加账号并发数量,从5改为10 + max_account_workers = 10 # 增加账号并发数 + printn(f"使用 {max_account_workers} 个线程并发处理账号登录") + + # 使用线程池处理多个账号登录 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_account_workers) as executor: + futures = [] + # chinaTelecomAccount = '' + for i in chinaTelecomAccount.split('\n'): + i = i.split('#') + phone = i[0] + password = i[-1] + uid = i[-1] + + # 提交登录任务到线程池 + future = executor.submit(process_account, phone, password, uid) + futures.append(future) + + # 等待所有登录任务完成,添加进度显示 + completed = 0 + total = len(futures) + for future in concurrent.futures.as_completed(futures): + try: + completed += 1 + printn(f"账号处理进度: {completed}/{total}") + future.result() + except Exception as e: + printn(f"账号处理异常: {str(e)}") + +# 处理单个账号的登录和抢购 +def process_account(phone, password, uid): + ticket = False + + if phone in load_token: + printn(f'{phone} 使用缓存登录') + ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token']) + + if ticket == False: + printn(f'{phone} 使用密码登录') + ticket = userLoginNormal(phone, password) + + if ticket: + ks(phone, ticket, uid) + else: + printn(f'{phone} 登录失败') + +jdhf008 = "" +cfcs = 60 # 并发数 +max_retries = 200 # 最大重试次数 +retry_timeout = 200 # 重试时的超时时间 +jdaid = '60dd79533dc03d3c76bdde30' +ck = {} +appToken = "" # 添加appToken全局变量 +load_token_file = 'chinaTelecom_cache.json' +try: + with open(load_token_file, 'r') as f: + load_token = json.load(f) +except: + load_token = {} + +if __name__ == "__main__": + main() \ No newline at end of file