imoutaiHelper.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. import base64
  2. import datetime
  3. import json
  4. import math
  5. import os
  6. import random
  7. import time
  8. import requests
  9. from Crypto.Cipher import AES
  10. from dailycheckin import CheckIn
  11. class Encrypt:
  12. def __init__(self, key, iv):
  13. self.key = key.encode("utf-8")
  14. self.iv = iv.encode("utf-8")
  15. def pkcs7padding(self, text):
  16. """明文使用PKCS7填充"""
  17. bs = 16
  18. length = len(text)
  19. bytes_length = len(text.encode("utf-8"))
  20. padding_size = length if (bytes_length == length) else bytes_length
  21. padding = bs - padding_size % bs
  22. padding_text = chr(padding) * padding
  23. self.coding = chr(padding)
  24. return text + padding_text
  25. def aes_encrypt(self, content):
  26. """AES加密"""
  27. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  28. content_padding = self.pkcs7padding(content)
  29. encrypt_bytes = cipher.encrypt(content_padding.encode("utf-8"))
  30. result = str(base64.b64encode(encrypt_bytes), encoding="utf-8")
  31. return result
  32. def aes_decrypt(self, content):
  33. """AES解密"""
  34. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  35. content = base64.b64decode(content)
  36. text = cipher.decrypt(content).decode("utf-8")
  37. return text.rstrip(self.coding)
  38. class IMAOTAI(CheckIn):
  39. name = "i茅台"
  40. def __init__(self, check_item):
  41. self.check_item = check_item
  42. self.RESERVE_RULE = 0
  43. self.mt_r = "clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/"
  44. self.ITEM_MAP = {
  45. "10941": "53%vol 500ml贵州茅台酒(甲辰龙年)",
  46. "10942": "53%vol 375ml×2贵州茅台酒(甲辰龙年)",
  47. "10056": "53%vol 500ml茅台1935",
  48. "2478": "53%vol 500ml贵州茅台酒(珍品)",
  49. }
  50. self.ITEM_CODES = ["10941", "10942"]
  51. AES_KEY = "qbhajinldepmucsonaaaccgypwuvcjaa"
  52. AES_IV = "2018534749963515"
  53. self.encrypt = Encrypt(key=AES_KEY, iv=AES_IV)
  54. self.mt_version = json.loads(
  55. requests.get("https://itunes.apple.com/cn/lookup?id=1600482450").text
  56. )["results"][0]["version"]
  57. self.headers = {}
  58. self.header_context = """
  59. MT-Lat: 28.499562
  60. MT-K: 1675213490331
  61. MT-Lng: 102.182324
  62. Host: app.moutai519.com.cn
  63. MT-User-Tag: 0
  64. Accept: */*
  65. MT-Network-Type: WIFI
  66. MT-Token: 1
  67. MT-Info: 028e7f96f6369cafe1d105579c5b9377
  68. MT-Device-ID: 2F2075D0-B66C-4287-A903-DBFF6358342A
  69. MT-Bundle-ID: com.moutai.mall
  70. Accept-Language: en-CN;q=1, zh-Hans-CN;q=0.9
  71. MT-Request-ID: 167560018873318465
  72. MT-APP-Version: 1.3.7
  73. User-Agent: iOS;16.3;Apple;?unrecognized?
  74. MT-R: clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw==
  75. Content-Length: 93
  76. Accept-Encoding: gzip, deflate, br
  77. Connection: keep-alive
  78. Content-Type: application/json
  79. userId: 2
  80. """
  81. def init_headers(
  82. self,
  83. user_id: str = "1",
  84. token: str = "2",
  85. lat: str = "29.83826",
  86. lng: str = "119.74375",
  87. ):
  88. for k in self.header_context.strip().split("\n"):
  89. temp_l = k.split(": ")
  90. dict.update(self.headers, {temp_l[0]: temp_l[1]})
  91. dict.update(self.headers, {"userId": user_id})
  92. dict.update(self.headers, {"MT-Token": token})
  93. dict.update(self.headers, {"MT-Lat": lat})
  94. dict.update(self.headers, {"MT-Lng": lng})
  95. dict.update(self.headers, {"MT-APP-Version": self.mt_version})
  96. def get_current_session_id(self):
  97. day_time = int(time.mktime(datetime.date.today().timetuple())) * 1000
  98. my_url = f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/{day_time}"
  99. responses = requests.get(my_url)
  100. if responses.status_code != 200:
  101. print(
  102. f"get_current_session_id : params : {day_time}, response code : {responses.status_code}, response body : {responses.text}"
  103. )
  104. current_session_id = responses.json()["data"]["sessionId"]
  105. dict.update(self.headers, {"current_session_id": str(current_session_id)})
  106. def get_map(self, lat: str = "28.499562", lng: str = "102.182324"):
  107. p_c_map = {}
  108. url = "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get"
  109. headers = {
  110. "X-Requested-With": "XMLHttpRequest",
  111. "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_1 like Mac OS X)",
  112. "Referer": "https://h5.moutai519.com.cn/gux/game/main?appConfig=2_1_2",
  113. "Client-User-Agent": "iOS;16.0.1;Apple;iPhone 14 ProMax",
  114. "MT-R": "clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw==",
  115. "Origin": "https://h5.moutai519.com.cn",
  116. "MT-APP-Version": self.mt_version,
  117. "MT-Request-ID": f"{int(time.time() * 1000)}{random.randint(1111111, 999999999)}{int(time.time() * 1000)}",
  118. "Accept-Language": "zh-CN,zh-Hans;q=1",
  119. "MT-Device-ID": f"{int(time.time() * 1000)}{random.randint(1111111, 999999999)}{int(time.time() * 1000)}",
  120. "Accept": "application/json, text/javascript, */*; q=0.01",
  121. "mt-lng": f"{lng}",
  122. "mt-lat": f"{lat}",
  123. }
  124. res = requests.get(url, headers=headers)
  125. mtshops = res.json().get("data", {}).get("mtshops_pc", {})
  126. urls = mtshops.get("url")
  127. r = requests.get(urls)
  128. for k, v in dict(r.json()).items():
  129. provinceName = v.get("provinceName")
  130. cityName = v.get("cityName")
  131. if not p_c_map.get(provinceName):
  132. p_c_map[provinceName] = {}
  133. if not p_c_map[provinceName].get(cityName, None):
  134. p_c_map[provinceName][cityName] = [k]
  135. else:
  136. p_c_map[provinceName][cityName].append(k)
  137. return p_c_map, dict(r.json())
  138. def max_shop(self, city, item_code, p_c_map, province, shops):
  139. max_count = 0
  140. max_shop_id = "0"
  141. shop_ids = p_c_map[province][city]
  142. for shop in shops:
  143. shopId = shop["shopId"]
  144. items = shop["items"]
  145. if shopId not in shop_ids:
  146. continue
  147. for item in items:
  148. if item["itemId"] != str(item_code):
  149. continue
  150. if item["inventory"] > max_count:
  151. max_count = item["inventory"]
  152. max_shop_id = shopId
  153. print(
  154. f"item code {item_code}, max shop id : {max_shop_id}, max count : {max_count}"
  155. )
  156. return max_shop_id
  157. def distance_shop(
  158. self,
  159. item_code,
  160. shops,
  161. source_data,
  162. lat: str = "28.499562",
  163. lng: str = "102.182324",
  164. ):
  165. temp_list = []
  166. for shop in shops:
  167. shopId = shop["shopId"]
  168. items = shop["items"]
  169. item_ids = [i["itemId"] for i in items]
  170. if str(item_code) not in item_ids:
  171. continue
  172. shop_info = source_data.get(shopId)
  173. d = math.sqrt(
  174. (float(lat) - shop_info["lat"]) ** 2
  175. + (float(lng) - shop_info["lng"]) ** 2
  176. )
  177. temp_list.append((d, shopId))
  178. temp_list = sorted(temp_list, key=lambda x: x[0])
  179. if len(temp_list) > 0:
  180. return temp_list[0][1]
  181. else:
  182. return "0"
  183. def get_location_count(
  184. self,
  185. province: str,
  186. city: str,
  187. item_code: str,
  188. p_c_map: dict,
  189. source_data: dict,
  190. lat: str = "29.83826",
  191. lng: str = "102.182324",
  192. reserve_rule: int = 0,
  193. ):
  194. day_time = int(time.mktime(datetime.date.today().timetuple())) * 1000
  195. session_id = self.headers["current_session_id"]
  196. responses = requests.get(
  197. f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/shop/list/slim/v3/{session_id}/{province}/{item_code}/{day_time}"
  198. )
  199. if responses.status_code != 200:
  200. print(
  201. f"get_location_count : params : {day_time}, response code : {responses.status_code}, response body : {responses.text}"
  202. )
  203. shops = responses.json()["data"]["shops"]
  204. if reserve_rule == 0:
  205. return self.distance_shop(item_code, shops, source_data, lat, lng)
  206. if reserve_rule == 1:
  207. return self.max_shop(city, item_code, p_c_map, province, shops)
  208. def act_params(self, shop_id: str, item_id: str):
  209. session_id = self.headers["current_session_id"]
  210. userId = self.headers["userId"]
  211. params = {
  212. "itemInfoList": [{"count": 1, "itemId": item_id}],
  213. "sessionId": int(session_id),
  214. "userId": userId,
  215. "shopId": shop_id,
  216. }
  217. s = json.dumps(params)
  218. act = self.encrypt.aes_encrypt(s)
  219. params.update({"actParam": act})
  220. return params
  221. def reservation(self, params: dict):
  222. params.pop("userId")
  223. responses = requests.post(
  224. "https://app.moutai519.com.cn/xhr/front/mall/reservation/add",
  225. json=params,
  226. headers=self.headers,
  227. ).json()
  228. if responses.get("code") == 401:
  229. msg = {
  230. "name": "申购结果",
  231. "value": "token失效, 请重新抓包获取",
  232. }
  233. elif responses.get("code") != 2000:
  234. msg = {
  235. "name": "申购结果",
  236. "value": responses.get("message"),
  237. }
  238. else:
  239. msg = {
  240. "name": "申购结果",
  241. "value": responses.get("data", {}).get("successDesc"),
  242. }
  243. return msg
  244. def getUserEnergyAward(self):
  245. """
  246. 耐力值
  247. """
  248. cookies = {
  249. "MT-Device-ID-Wap": self.headers["MT-Device-ID"],
  250. "MT-Token-Wap": self.headers["MT-Token"],
  251. "YX_SUPPORT_WEBP": "1",
  252. }
  253. response = requests.post(
  254. url="https://h5.moutai519.com.cn/game/isolationPage/getUserEnergyAward",
  255. cookies=cookies,
  256. headers=self.headers,
  257. json={},
  258. ).json()
  259. if response.get("code") == 200:
  260. msg = {
  261. "name": "耐力",
  262. "value": "✅领取耐力成功",
  263. }
  264. else:
  265. msg = {
  266. "name": "耐力",
  267. "value": response.get("message"),
  268. }
  269. return msg
  270. def main(self):
  271. msg = []
  272. mobile = self.check_item.get("mobile")
  273. province = self.check_item.get("province")
  274. city = self.check_item.get("city")
  275. token = self.check_item.get("token")
  276. userId = self.check_item.get("userid")
  277. lat = self.check_item.get("lat")
  278. lng = self.check_item.get("lng")
  279. item_codes = self.check_item.get("item_codes", self.ITEM_CODES)
  280. reserve_rule = self.check_item.get("reserve_rule", 0)
  281. msg = [
  282. {
  283. "name": "手机号",
  284. "value": f"{mobile}",
  285. },
  286. {
  287. "name": "省份城市",
  288. "value": f"{province}{city}",
  289. },
  290. ]
  291. p_c_map, source_data = self.get_map(lat=lat, lng=lng)
  292. self.get_current_session_id()
  293. self.init_headers(user_id=userId, token=token, lng=lng, lat=lat)
  294. try:
  295. for item in item_codes:
  296. max_shop_id = self.get_location_count(
  297. province=province,
  298. city=city,
  299. item_code=item,
  300. p_c_map=p_c_map,
  301. source_data=source_data,
  302. lat=lat,
  303. lng=lng,
  304. reserve_rule=reserve_rule,
  305. )
  306. if max_shop_id == "0":
  307. continue
  308. reservation_params = self.act_params(max_shop_id, item)
  309. reservation_msg = self.reservation(reservation_params)
  310. time.sleep(20)
  311. award_msg = self.getUserEnergyAward()
  312. msg.append(reservation_msg)
  313. msg.append(award_msg)
  314. except BaseException as e:
  315. msg.append(
  316. {
  317. "name": "申购结果",
  318. "value": e,
  319. }
  320. )
  321. msg = "\n".join([f"{one.get('name')}: {one.get('value')}" for one in msg])
  322. return msg
  323. if __name__ == "__main__":
  324. with open(
  325. os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json"),
  326. encoding="utf-8",
  327. ) as f:
  328. datas = json.loads(f.read())
  329. _check_item = datas.get("IMAOTAI", [])[0]
  330. print(IMAOTAI(check_item=_check_item).main())