import base64 import datetime import json import math import os import random import time import requests from Crypto.Cipher import AES from dailycheckin import CheckIn class Encrypt: def __init__(self, key, iv): self.key = key.encode("utf-8") self.iv = iv.encode("utf-8") def pkcs7padding(self, text): """明文使用PKCS7填充""" bs = 16 length = len(text) bytes_length = len(text.encode("utf-8")) padding_size = length if (bytes_length == length) else bytes_length padding = bs - padding_size % bs padding_text = chr(padding) * padding self.coding = chr(padding) return text + padding_text def aes_encrypt(self, content): """AES加密""" cipher = AES.new(self.key, AES.MODE_CBC, self.iv) content_padding = self.pkcs7padding(content) encrypt_bytes = cipher.encrypt(content_padding.encode("utf-8")) result = str(base64.b64encode(encrypt_bytes), encoding="utf-8") return result def aes_decrypt(self, content): """AES解密""" cipher = AES.new(self.key, AES.MODE_CBC, self.iv) content = base64.b64decode(content) text = cipher.decrypt(content).decode("utf-8") return text.rstrip(self.coding) class IMAOTAI(CheckIn): name = "i茅台" def __init__(self, check_item): self.check_item = check_item self.RESERVE_RULE = 0 self.mt_r = "clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/" self.ITEM_MAP = { "10941": "53%vol 500ml贵州茅台酒(甲辰龙年)", "10942": "53%vol 375ml×2贵州茅台酒(甲辰龙年)", "10056": "53%vol 500ml茅台1935", "2478": "53%vol 500ml贵州茅台酒(珍品)", } self.ITEM_CODES = ["10941", "10942"] AES_KEY = "qbhajinldepmucsonaaaccgypwuvcjaa" AES_IV = "2018534749963515" self.encrypt = Encrypt(key=AES_KEY, iv=AES_IV) self.mt_version = json.loads( requests.get("https://itunes.apple.com/cn/lookup?id=1600482450").text )["results"][0]["version"] self.headers = {} self.header_context = """ MT-Lat: 28.499562 MT-K: 1675213490331 MT-Lng: 102.182324 Host: app.moutai519.com.cn MT-User-Tag: 0 Accept: */* MT-Network-Type: WIFI MT-Token: 1 MT-Info: 028e7f96f6369cafe1d105579c5b9377 MT-Device-ID: 2F2075D0-B66C-4287-A903-DBFF6358342A MT-Bundle-ID: com.moutai.mall Accept-Language: en-CN;q=1, zh-Hans-CN;q=0.9 MT-Request-ID: 167560018873318465 MT-APP-Version: 1.3.7 User-Agent: iOS;16.3;Apple;?unrecognized? MT-R: clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw== Content-Length: 93 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Type: application/json userId: 2 """ def init_headers( self, user_id: str = "1", token: str = "2", lat: str = "29.83826", lng: str = "119.74375", ): for k in self.header_context.strip().split("\n"): temp_l = k.split(": ") dict.update(self.headers, {temp_l[0]: temp_l[1]}) dict.update(self.headers, {"userId": user_id}) dict.update(self.headers, {"MT-Token": token}) dict.update(self.headers, {"MT-Lat": lat}) dict.update(self.headers, {"MT-Lng": lng}) dict.update(self.headers, {"MT-APP-Version": self.mt_version}) def get_current_session_id(self): day_time = int(time.mktime(datetime.date.today().timetuple())) * 1000 my_url = f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/{day_time}" responses = requests.get(my_url) if responses.status_code != 200: print( f"get_current_session_id : params : {day_time}, response code : {responses.status_code}, response body : {responses.text}" ) current_session_id = responses.json()["data"]["sessionId"] dict.update(self.headers, {"current_session_id": str(current_session_id)}) def get_map(self, lat: str = "28.499562", lng: str = "102.182324"): p_c_map = {} url = "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get" headers = { "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_1 like Mac OS X)", "Referer": "https://h5.moutai519.com.cn/gux/game/main?appConfig=2_1_2", "Client-User-Agent": "iOS;16.0.1;Apple;iPhone 14 ProMax", "MT-R": "clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw==", "Origin": "https://h5.moutai519.com.cn", "MT-APP-Version": self.mt_version, "MT-Request-ID": f"{int(time.time() * 1000)}{random.randint(1111111, 999999999)}{int(time.time() * 1000)}", "Accept-Language": "zh-CN,zh-Hans;q=1", "MT-Device-ID": f"{int(time.time() * 1000)}{random.randint(1111111, 999999999)}{int(time.time() * 1000)}", "Accept": "application/json, text/javascript, */*; q=0.01", "mt-lng": f"{lng}", "mt-lat": f"{lat}", } res = requests.get(url, headers=headers) mtshops = res.json().get("data", {}).get("mtshops_pc", {}) urls = mtshops.get("url") r = requests.get(urls) for k, v in dict(r.json()).items(): provinceName = v.get("provinceName") cityName = v.get("cityName") if not p_c_map.get(provinceName): p_c_map[provinceName] = {} if not p_c_map[provinceName].get(cityName, None): p_c_map[provinceName][cityName] = [k] else: p_c_map[provinceName][cityName].append(k) return p_c_map, dict(r.json()) def max_shop(self, city, item_code, p_c_map, province, shops): max_count = 0 max_shop_id = "0" shop_ids = p_c_map[province][city] for shop in shops: shopId = shop["shopId"] items = shop["items"] if shopId not in shop_ids: continue for item in items: if item["itemId"] != str(item_code): continue if item["inventory"] > max_count: max_count = item["inventory"] max_shop_id = shopId print( f"item code {item_code}, max shop id : {max_shop_id}, max count : {max_count}" ) return max_shop_id def distance_shop( self, item_code, shops, source_data, lat: str = "28.499562", lng: str = "102.182324", ): temp_list = [] for shop in shops: shopId = shop["shopId"] items = shop["items"] item_ids = [i["itemId"] for i in items] if str(item_code) not in item_ids: continue shop_info = source_data.get(shopId) d = math.sqrt( (float(lat) - shop_info["lat"]) ** 2 + (float(lng) - shop_info["lng"]) ** 2 ) temp_list.append((d, shopId)) temp_list = sorted(temp_list, key=lambda x: x[0]) if len(temp_list) > 0: return temp_list[0][1] else: return "0" def get_location_count( self, province: str, city: str, item_code: str, p_c_map: dict, source_data: dict, lat: str = "29.83826", lng: str = "102.182324", reserve_rule: int = 0, ): day_time = int(time.mktime(datetime.date.today().timetuple())) * 1000 session_id = self.headers["current_session_id"] responses = requests.get( f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/shop/list/slim/v3/{session_id}/{province}/{item_code}/{day_time}" ) if responses.status_code != 200: print( f"get_location_count : params : {day_time}, response code : {responses.status_code}, response body : {responses.text}" ) shops = responses.json()["data"]["shops"] if reserve_rule == 0: return self.distance_shop(item_code, shops, source_data, lat, lng) if reserve_rule == 1: return self.max_shop(city, item_code, p_c_map, province, shops) def act_params(self, shop_id: str, item_id: str): session_id = self.headers["current_session_id"] userId = self.headers["userId"] params = { "itemInfoList": [{"count": 1, "itemId": item_id}], "sessionId": int(session_id), "userId": userId, "shopId": shop_id, } s = json.dumps(params) act = self.encrypt.aes_encrypt(s) params.update({"actParam": act}) return params def reservation(self, params: dict): params.pop("userId") responses = requests.post( "https://app.moutai519.com.cn/xhr/front/mall/reservation/add", json=params, headers=self.headers, ).json() if responses.get("code") == 401: msg = { "name": "申购结果", "value": "token失效, 请重新抓包获取", } elif responses.get("code") != 2000: msg = { "name": "申购结果", "value": responses.get("message"), } else: msg = { "name": "申购结果", "value": responses.get("data", {}).get("successDesc"), } return msg def getUserEnergyAward(self): """ 耐力值 """ cookies = { "MT-Device-ID-Wap": self.headers["MT-Device-ID"], "MT-Token-Wap": self.headers["MT-Token"], "YX_SUPPORT_WEBP": "1", } response = requests.post( url="https://h5.moutai519.com.cn/game/isolationPage/getUserEnergyAward", cookies=cookies, headers=self.headers, json={}, ).json() if response.get("code") == 200: msg = { "name": "耐力", "value": "✅领取耐力成功", } else: msg = { "name": "耐力", "value": response.get("message"), } return msg def main(self): msg = [] mobile = self.check_item.get("mobile") province = self.check_item.get("province") city = self.check_item.get("city") token = self.check_item.get("token") userId = self.check_item.get("userid") lat = self.check_item.get("lat") lng = self.check_item.get("lng") item_codes = self.check_item.get("item_codes", self.ITEM_CODES) reserve_rule = self.check_item.get("reserve_rule", 0) msg = [ { "name": "手机号", "value": f"{mobile}", }, { "name": "省份城市", "value": f"{province}{city}", }, ] p_c_map, source_data = self.get_map(lat=lat, lng=lng) self.get_current_session_id() self.init_headers(user_id=userId, token=token, lng=lng, lat=lat) try: for item in item_codes: max_shop_id = self.get_location_count( province=province, city=city, item_code=item, p_c_map=p_c_map, source_data=source_data, lat=lat, lng=lng, reserve_rule=reserve_rule, ) if max_shop_id == "0": continue reservation_params = self.act_params(max_shop_id, item) reservation_msg = self.reservation(reservation_params) time.sleep(20) award_msg = self.getUserEnergyAward() msg.append(reservation_msg) msg.append(award_msg) except BaseException as e: msg.append( { "name": "申购结果", "value": e, } ) msg = "\n".join([f"{one.get('name')}: {one.get('value')}" for one in msg]) return msg if __name__ == "__main__": with open( os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json"), encoding="utf-8", ) as f: datas = json.loads(f.read()) _check_item = datas.get("IMAOTAI", [])[0] print(IMAOTAI(check_item=_check_item).main())