From 326c416f5c40def8160561d1f3375a267fe2d5e0 Mon Sep 17 00:00:00 2001 From: XXhaos Date: Thu, 23 Apr 2026 17:22:20 +0800 Subject: [PATCH] Add files via upload --- .../mitm_xbox_addon.cpython-314.pyc | Bin 0 -> 35266 bytes mitmproxy/addons/mitm_xbox_addon.py | 732 ++++++++++++++++++ mitmproxy/docker-compose.yml | 65 ++ mitmproxy/mitm-data/mitmproxy-ca-cert.cer | 20 + mitmproxy/mitm-data/mitmproxy-ca-cert.p12 | Bin 0 -> 1035 bytes mitmproxy/mitm-data/mitmproxy-ca-cert.pem | 20 + mitmproxy/mitm-data/mitmproxy-ca.p12 | Bin 0 -> 2384 bytes mitmproxy/mitm-data/mitmproxy-ca.pem | 47 ++ mitmproxy/mitm-data/mitmproxy-dhparam.pem | 14 + mitmproxy/mitm-data/state.json | 9 + mitmproxy/xboxbot/Dockerfile | 12 + mitmproxy/xboxbot/bot.py | 319 ++++++++ 12 files changed, 1238 insertions(+) create mode 100644 mitmproxy/addons/__pycache__/mitm_xbox_addon.cpython-314.pyc create mode 100644 mitmproxy/addons/mitm_xbox_addon.py create mode 100644 mitmproxy/docker-compose.yml create mode 100644 mitmproxy/mitm-data/mitmproxy-ca-cert.cer create mode 100644 mitmproxy/mitm-data/mitmproxy-ca-cert.p12 create mode 100644 mitmproxy/mitm-data/mitmproxy-ca-cert.pem create mode 100644 mitmproxy/mitm-data/mitmproxy-ca.p12 create mode 100644 mitmproxy/mitm-data/mitmproxy-ca.pem create mode 100644 mitmproxy/mitm-data/mitmproxy-dhparam.pem create mode 100644 mitmproxy/mitm-data/state.json create mode 100644 mitmproxy/xboxbot/Dockerfile create mode 100644 mitmproxy/xboxbot/bot.py diff --git a/mitmproxy/addons/__pycache__/mitm_xbox_addon.cpython-314.pyc b/mitmproxy/addons/__pycache__/mitm_xbox_addon.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4523c2549010395122c6751219208f679057f76c GIT binary patch literal 35266 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 强制“正常通知”(不要静默)。 + 即使失败也不会中断 mitmproxy,只写日志。 + """ + try: + payload = { + "chat_id": TELEGRAM_CHAT_ID, + "text": text, + "parse_mode": "HTML", # 可以放简单加粗/表情 + "disable_web_page_preview": True, + "disable_notification": False # 非静默,尽量触发弹窗 + } + data = json.dumps(payload, ensure_ascii=False).encode("utf-8") + req = urlrequest.Request( + f"{TELEGRAM_API_BASE}/bot{TELEGRAM_BOT_TOKEN}/sendMessage", + data=data, + method="POST" + ) + req.add_header("Content-Type", "application/json") + # 走 HTTPS + ctx_ssl = ssl.create_default_context() + with urlrequest.urlopen(req, context=ctx_ssl, timeout=10) as resp: + # 读一下结果防止连接挂着 + _ = resp.read() + ctx.log.info("[tg] sent notification ok") + except Exception as ex: + ctx.log.warn(f"[tg] send failed: {ex}") + + +# ========================= +# 状态持久化 (state.json) +# ========================= +DEFAULT_STATE_PATHS = [ + "/home/mitmproxy/.mitmproxy/state.json", + "/opt/mitmproxy/state.json", + "/data/mitmproxy/state.json", + os.path.join(os.path.dirname(__file__), "state.json"), +] + +def _pick_state_path(): + for p in DEFAULT_STATE_PATHS: + d = os.path.dirname(p) + try: + if d and not os.path.exists(d): + os.makedirs(d, exist_ok=True) + return p + except Exception: + continue + return "state.json" + +STATE_PATH = _pick_state_path() + +def _load_state(): + try: + with open(STATE_PATH, "r", encoding="utf-8") as f: + return json.load(f) + except Exception: + return {} + +def _save_state(st): + tmp = STATE_PATH + ".tmp" + with open(tmp, "w", encoding="utf-8") as f: + json.dump(st, f, ensure_ascii=False, indent=2, sort_keys=True) + os.replace(tmp, STATE_PATH) + +def getv(key, default=""): + st = _load_state() + return st.get(key, default) + +def setv(key, value): + st = _load_state() + st[key] = value + _save_state(st) + + +# ========================= +# 三元组存取 (ProductId, SkuId, AvailabilityId) +# ========================= +_PRODUCTS_KEY = "__xbox_products__" # list[ {ProductId,SkuId,AvailabilityId} ] +_LISTSTR_KEY = "XboxProductList" # "product1=pid|sku|aid;product2=..." + +def _get_products(): + data = getv(_PRODUCTS_KEY, []) + if not isinstance(data, list): + return [] + out = [] + for it in data: + try: + p = str(it.get("ProductId", "")).strip() + s = str(it.get("SkuId", "")).strip() + a = str(it.get("AvailabilityId", "")).strip() + if p and s and a: + out.append({"ProductId": p, "SkuId": s, "AvailabilityId": a}) + except Exception: + continue + return out + +def _save_products(lst): + """ + - 去重 + - 回写到 state.json (__xbox_products__ / XboxProductList) + - 如果 XboxProductList 有变化 -> 主动 Telegram 推送 + """ + # 之前的 product list 串,等会对比判断是否更新 + old_list_str = getv(_LISTSTR_KEY, "") + + # 去重 + seen = set() + uniq = [] + for it in lst: + key = f"{it['ProductId']}||{it['SkuId']}||{it['AvailabilityId']}" + if key in seen: + continue + seen.add(key) + uniq.append(it) + + # 写入内存状态 + setv(_PRODUCTS_KEY, uniq) + + # 同步生成 XboxProductList 串 + parts = [] + for i, it in enumerate(uniq, 1): + parts.append(f"product{i}={it['ProductId']}|{it['SkuId']}|{it['AvailabilityId']}") + new_list_str = ";".join(parts) + setv(_LISTSTR_KEY, new_list_str) + + # 如果产品列表字符串发生变化 -> 发通知 + if new_list_str != old_list_str: + count_now = len(uniq) + _tg_send(f"🔔 XboxProductList 更新\n当前产品数: {count_now}") + +def _add_product(pid, sid, aid): + if not (pid and sid and aid): + return False + cur = _get_products() + key = f"{pid}||{sid}||{aid}" + if any((x["ProductId"]+"||"+x["SkuId"]+"||"+x["AvailabilityId"]) == key for x in cur): + return False + cur.append({"ProductId": pid, "SkuId": sid, "AvailabilityId": aid}) + _save_products(cur) + return True + + +# ========================= +# HTTP JSON helper +# ========================= +def _json_response(flow: http.HTTPFlow, data: dict, status: int = 200): + body = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8") + flow.response = http.Response.make( + status, + body, + {"Content-Type": "application/json; charset=utf-8"}, + ) + + +# ========================= +# 抽三元组的递归解析 +# ========================= +def _extract_triples_from_json_like(text: str): + out = set() + if not text: + return out + data = None + try: + data = json.loads(text) + except Exception: + # 某些接口会返回 ")]}',\n{json...}" 之类,尝试抓第一个 {...} 或 [...] + m = re.search(r"(\{[\s\S]*\}|\[[\s\S]*\])", text) + if m: + try: + data = json.loads(m.group(1)) + except Exception: + pass + if data is None: + return out + + def is_obj(v): + return isinstance(v, dict) + + def visit(node): + if isinstance(node, list): + for v in node: + visit(v) + return + if not is_obj(node): + return + at = node.get("actionType") + if isinstance(at, str) and at.lower() == "cart": + args = node.get("actionArguments", {}) + if is_obj(args): + p = str(args.get("ProductId", "")).strip() + s = str(args.get("SkuId", "")).strip() + a = str(args.get("AvailabilityId", "")).strip() + if p and s and a: + out.add((p, s, a)) + for v in node.values(): + visit(v) + + visit(data) + return out + + +# ========================= +# PUT /cart/loadCart 调用工具 (加购) +# ========================= +UA = ( + "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/133.0.3065.54 " + "Version/18.0 Mobile/15E148 Safari/604.1" +) + +CART_API_URL = ( + "https://cart.production.store-web.dynamics.com/" + "cart/v1.0/cart/loadCart?cartType=consumer&appId=StoreWeb" +) + +def _cart_put_single(muid: str, ms_cv: str, pid: str, sid: str, aid: str, x_vec: str = ""): + """ + 向微软的购物车接口发一次 PUT, 把 (ProductId, SkuId, AvailabilityId) 放进去。 + """ + payload = { + "locale": "en-ng", + "market": "NG", + "catalogClientType": "storeWeb", + "friendlyName": "cart-NG", + "riskSessionId": str(uuid.uuid4()), + "clientContext": {"client": "UniversalWebStore.Cart", "deviceType": "Pc"}, + "itemsToAdd": { + "items": [ + { + "productId": pid, + "skuId": sid, + "availabilityId": aid, + "campaignId": "xboxcomct", + "quantity": 1, + } + ] + }, + } + data = json.dumps(payload, separators=(",", ":")).encode("utf-8") + req = urlrequest.Request(CART_API_URL, data=data, method="PUT") + req.add_header("content-type", "application/json") + req.add_header("accept", "*/*") + req.add_header("x-authorization-muid", muid or "") + req.add_header("x-validation-field-1", "9pgbhbppjf2b") + req.add_header("ms-cv", ms_cv or "") + if x_vec: + req.add_header("x-ms-vector-id", x_vec) + req.add_header("accept-language", "en-US,en;q=0.9") + req.add_header("accept-encoding", "gzip, deflate, br") + req.add_header("sec-fetch-site", "cross-site") + req.add_header("sec-fetch-mode", "cors") + req.add_header("sec-fetch-dest", "empty") + req.add_header("origin", "https://www.microsoft.com") + req.add_header("referer", "https://www.microsoft.com/") + req.add_header("user-agent", UA) + + try: + ctx_ssl = ssl.create_default_context() + with urlrequest.urlopen(req, context=ctx_ssl, timeout=20) as resp: + return resp.status, (200 <= resp.status < 300), resp.read()[:2048] + except Exception as ex: + return 0, False, str(ex).encode("utf-8", errors="ignore") + + +# ========================= +# 主插件 +# ========================= +class MitmXboxAddonNoDashV4: + TARGET_IDS = [ + "9PNTSH5SKCL5", + "9nfmccp0pm67", + "9npbvj8lwsvn", + "9pcgszz8zpq2", + "9P54FF0VQD7R", + "9NCJZN3LBD3P", + ] + RX_XBOX_PATH = re.compile(r"^/([A-Za-z]{2}-[A-Za-z]{2})(/.*)$") + + def load(self, loader): + ctx.log.info(f"[xbox-nodash-v4] State path: {STATE_PATH}") + + # ----------------- + # 本地管理端点 /__xbox/* + # ----------------- + def _normalize_seg(self, raw_path: str): + if not raw_path or not raw_path.lower().startswith("/__xbox/"): + return "", "" + seg = raw_path[len("/__xbox/") :].split("?")[0] + rawseg = seg.strip("/") + normseg = re.sub(r"[^a-z0-9]", "", rawseg.lower()) + return rawseg, normseg + + def _handle_admin(self, flow: http.HTTPFlow): + path = flow.request.path or "/" + rawseg, seg = self._normalize_seg(path) + if not seg: + return False + + # /__xbox/check + if seg == "check": + keys = [ + "cart-x-authorization-muid", + "cart-ms-cv", + "cart-x-ms-vector-id", + "authorization", + "cartId", + ] + state = { + k: getv("fiddler.custom." + k, "") + if k not in ("authorization", "cartId") + else getv(k, "") + for k in keys + } + prods = _get_products() + _json_response( + flow, + { + "ok": True, + "state": state, + "products_count": len(prods), + "XboxProductList": getv(_LISTSTR_KEY, ""), + }, + ) + return True + + # /__xbox/viewproducts + if seg == "viewproducts": + prods = _get_products() + _json_response( + flow, + { + "ok": True, + "count": len(prods), + "products": prods, + "XboxProductList": getv(_LISTSTR_KEY, ""), + }, + ) + return True + + # /__xbox/clearproducts + if seg == "clearproducts": + _save_products([]) # 这个里面也会触发Telegram通知(产品数=0) + _json_response(flow, {"ok": True, "message": "cleared"}) + return True + + # /__xbox/addtocart + if seg == "addtocart": + prods = _get_products() + if not prods: + _json_response( + flow, {"ok": False, "error": "no products captured"}, 400 + ) + return True + + muid = getv("fiddler.custom.cart-x-authorization-muid", "") + ms_cv = getv("fiddler.custom.cart-ms-cv", "") + x_vec = getv("fiddler.custom.cart-x-ms-vector-id", "") + + if not muid or not ms_cv: + _json_response( + flow, + { + "ok": False, + "error": "missing cart-x-authorization-muid / cart-ms-cv", + }, + 400, + ) + return True + + successes, failures = [], [] + remaining = list(prods) + + for item in prods: + pid = item["ProductId"] + sid = item["SkuId"] + aid = item["AvailabilityId"] + id_triple = f"{pid}/{sid}/{aid}" + + code, ok, _ = _cart_put_single(muid, ms_cv, pid, sid, aid, x_vec) + if ok: + successes.append({"triple": id_triple, "status": code}) + # 从 remaining 里移除成功的 + remaining = [ + x + for x in remaining + if not ( + x["ProductId"] == pid + and x["SkuId"] == sid + and x["AvailabilityId"] == aid + ) + ] + else: + failures.append({"triple": id_triple, "status": code or "ERR"}) + + time.sleep(0.15) + + # 写回剩下(没成功的还保留),会触发 _save_products 内部逻辑 + _save_products(remaining) + + _json_response( + flow, + { + "ok": True, + "tried": len(prods), + "success": len(successes), + "failed": len(failures), + "failures": failures, + "remaining": len(remaining), + }, + ) + return True + + _json_response( + flow, + { + "ok": False, + "error": "unknown admin action", + "seen_seg": seg, + "raw_path": path, + "rawseg": rawseg, + }, + 404, + ) + return True + + # ----------------- + # 自动 302 跳转规则 + # ----------------- + def _maybe_redirect(self, flow: http.HTTPFlow): + fullurl = (flow.request.pretty_url or "").strip() + if not fullurl: + return False + + # (0) Microsoft Store → Xbox corehalo/ + # 匹配 https://www.microsoft.com/en-us/store/...ID(12位)... + # 跳转到 https://www.xbox.com/en-us/games/store/corehalo/ID + try: + m_ms = re.search( + r"(?i)^https?://(?:www\.)?microsoft\.com/en-us/store/.*?([A-Za-z0-9]{12})(?:[\/?#]|$)", + fullurl, + ) + if m_ms: + game_id = m_ms.group(1) + new_url = "https://www.xbox.com/en-us/games/store/corehalo/" + game_id + ctx.log.info(f"[xbox-nodash-v4] Redirect microsoft.com -> {new_url}") + html_body = ( + "Redirecting to " + + new_url + + "" + ).encode("utf-8", errors="ignore") + flow.response = http.Response.make( + 302, + html_body, + { + "Location": new_url, + "Content-Type": "text/html; charset=UTF-8", + }, + ) + return True + except Exception as e: + ctx.log.warn(f"[xbox-nodash-v4] ms store redirect error: {e}") + + # 继续原有的重定向逻辑 + parsed = urlparse(fullurl) + host = parsed.hostname or "" + path = parsed.path or "" + query = parsed.query or "" + qd = parse_qs(query, keep_blank_values=True) + + # (1) xbox.com 区域纠正成 en-us + if host.lower() == "www.xbox.com": + m = self.RX_XBOX_PATH.match(path) + if m: + region = m.group(1) + suffix = m.group(2) or "/" + if region.lower() not in {"es-ar", "en-us"}: + new = f"https://www.xbox.com/en-us{suffix}" + ctx.log.info(f"[xbox-nodash-v4] Redirect xbox.com {fullurl} -> {new}") + flow.response = http.Response.make(302, b"", {"Location": new}) + return True + + # (2) xboxfan.com/goto?region=xx → 改成 en-us + if host.lower() == "xboxfan.com" and path.lower().startswith("/goto"): + region_vals = qd.get("region", []) + if region_vals and region_vals[0].lower() not in {"es-ar", "en-us"}: + qd["region"] = ["en-us"] + new_q = urlencode(qd, doseq=True) + new = urlunparse(("https", host, path, "", new_q, "")) + ctx.log.info(f"[xbox-nodash-v4] Redirect xboxfan {fullurl} -> {new}") + flow.response = http.Response.make(302, b"", {"Location": new}) + return True + + # (3) app.corehalo.com/ms/link/go?r=xx → 改成 en-us + if host.lower() == "app.corehalo.com" and path.lower().startswith("/ms/link/go"): + r_vals = qd.get("r", []) + if r_vals and r_vals[0].lower() not in {"es-ar", "en-us"}: + qd["r"] = ["en-us"] + new_q = urlencode(qd, doseq=True) + new = urlunparse(("https", host, path, "", new_q, "")) + ctx.log.info(f"[xbox-nodash-v4] Redirect corehalo {fullurl} -> {new}") + flow.response = http.Response.make(302, b"", {"Location": new}) + return True + + return False + + # ----------------- + # request 钩子 + # ----------------- + def request(self, flow: http.HTTPFlow): + # 先跑跳转 + try: + if self._maybe_redirect(flow): + return + except Exception as e: + ctx.log.warn(f"[xbox-nodash-v4] redirect check error: {e}") + + # 管理端点 + if self._handle_admin(flow): + return + + # 拦截并修补 RequestParentalApproval:只改 Authorization 和 cartId + if ( + flow.request.host == "buynow.production.store-web.dynamics.com" + and flow.request.method == "POST" + and "/v1.0/Cart/RequestParentalApproval" in flow.request.path + ): + cart_id_stored = getv("cartId", "") + auth_stored = getv("authorization", "") + if not cart_id_stored or not auth_stored: + ctx.log.error("[xbox-nodash-v4] 缺少存储的 cartId 或 authorization!") + return + + # 覆盖 Authorization 头 + flow.request.headers["Authorization"] = auth_stored + + # 覆盖请求体里的 cartId + try: + body = flow.request.get_text() or "" + if body: + new_body = re.sub( + r'"cartId"\s*:\s*"[^"]*"', + f'"cartId":"{cart_id_stored}"', + body, + ) + if new_body != body: + flow.request.set_text(new_body) + ctx.log.info("[xbox-nodash-v4] 替换 cartId") + else: + ctx.log.warn("[xbox-nodash-v4] 未发现 cartId 字段,仅替换 Authorization") + except Exception as ex: + ctx.log.warn(f"[xbox-nodash-v4] 替换 cartId 失败: {ex}") + + host = flow.request.host or "" + path = flow.request.path or "" + fullurl = flow.request.pretty_url or "" + + # family 请求拦截 + if ( + host == "account.microsoft.com" + and flow.request.method == "POST" + and path.startswith("/family/api/buy/requests/complete") + ): + body_low = (flow.request.get_text() or "").lower() + for tid in self.TARGET_IDS: + if tid.lower() in body_low: + flow.response = http.Response.make( + 403, + b"Blocked by mitmproxy rule", + {"Content-Type": "text/plain; charset=utf-8"}, + ) + ctx.log.info(f"[xbox-nodash-v4] Blocked family complete for productId: {tid}") + return + + # 购物车 body 规范化(强制 NG / en-ng / cart-NG) + if host.endswith("store-web.dynamics.com") and "/v1.0/cart" in path: + try: + txt = flow.request.get_text() or "" + if not txt.strip(): + return + modified = False + try: + data = json.loads(txt) + mk = data.get("market") + lc = data.get("locale") + fn = data.get("friendlyName") + if mk and mk.upper() != "AR": + data["market"] = "NG"; modified = True + if lc and lc.lower() != "es-ar": + data["locale"] = "en-ng"; modified = True + if fn and fn != "cart-AR": + data["friendlyName"] = "cart-NG"; modified = True + if modified: + flow.request.set_text(json.dumps(data, ensure_ascii=False)) + ctx.log.info("[xbox-nodash-v4] cart body normalized to NG") + except Exception: + new = re.sub(r'"market"\s*:\s*"(?!ar|AR)\w{2}"', r'"market":"NG"', txt) + new = re.sub(r'"locale"\s*:\s*"(?!es-ar|es-AR)[\w-]+"', r'"locale":"en-NG"', new) + new = re.sub(r'"friendlyName"\s*:\s*"(?!cart-AR)cart-\w{2}"', r'"friendlyName":"cart-NG"', new) + if new != txt: + flow.request.set_text(new) + ctx.log.info("[xbox-nodash-v4] cart body normalized by regex") + except Exception as ex: + ctx.log.warn(f"[xbox-nodash-v4] cart normalization failed: {ex}") + + # eligibilityCheck:抓 Authorization 和 cartId + if ( + flow.request.method.upper() == "PUT" + and "eligibilitycheck" in path.lower() + and "cart.production.store-web.dynamics.com" in host + ): + # 授权头直接存 + auth = flow.request.headers.get("Authorization", "") + if auth: + setv("authorization", auth) + + # 抓 cartId(query param 里) + try: + qs = parse_qs(urlparse(fullurl).query) + cart_id = qs.get("cartId", [""])[0] or qs.get("cartid", [""])[0] + if cart_id: + cart_id = unquote(cart_id) + old_cart = getv("cartId", "") + if cart_id != old_cart: + setv("cartId", cart_id) + # cartId 变更 -> 主动推送 + _tg_send(f"🔔 cartId 更新:\n{cart_id}") + except Exception: + pass + + ctx.log.info("[xbox-nodash-v4] captured eligibilityCheck params") + + # loadCart:抓购物车请求头 + if ( + flow.request.method.upper() == "PUT" + and "/v1.0/cart/loadCart" in path + and "cart.production.store-web.dynamics.com" in host + ): + for hk, sk in [ + ("X-Authorization-Muid", "fiddler.custom.cart-x-authorization-muid"), + ("MS-CV", "fiddler.custom.cart-ms-cv"), + ("X-MS-Vector-Id", "fiddler.custom.cart-x-ms-vector-id"), + ]: + val = flow.request.headers.get(hk) + if val: + setv(sk, val) + ctx.log.info("[xbox-nodash-v4] captured loadCart headers") + + # ----------------- + # response 钩子 + # ----------------- + def response(self, flow: http.HTTPFlow): + req, resp = flow.request, flow.response + host = (req.host or "").lower() + path = (req.path or "").lower() + + # 抓取 productActions(emerald.xboxservices.com),提取三元组 -> _add_product + try: + if ( + req.method.upper() == "GET" + and host == "emerald.xboxservices.com" + and path.startswith("/xboxcomfd/productactions/") + ): + # 我们只要 locale=en-us 的 + if "locale=en-us" in (req.pretty_url or "").lower(): + txt = resp.get_text() or "" + triples = _extract_triples_from_json_like(txt) + if triples: + added_cnt = 0 + for p, s, a in triples: + if _add_product(p, s, a): + added_cnt += 1 + ctx.log.info( + f"[xbox-nodash-v4] productActions captured: +{added_cnt}, total={len(_get_products())}" + ) + except Exception as e: + ctx.log.warn(f"[xbox-nodash-v4] capture triples error: {e}") + + # family product endpoint:把 productKind 改成 "Game" + try: + fullurl = (req.pretty_url or "").strip() + if fullurl.lower().startswith( + "https://account.microsoft.com/family/api/product?puid=" + ): + txt = "" + try: + txt = resp.get_text() + except Exception: + try: + txt = resp.content.decode("utf-8", errors="replace") + except Exception: + txt = "" + if not txt: + return + try: + data = json.loads(txt) + except Exception as ex: + ctx.log.warn(f"[xbox-nodash-v4] response body not JSON: {ex}") + return + try: + if "productDocument" not in data or not isinstance(data["productDocument"], dict): + data["productDocument"] = {} + pd = data["productDocument"] + if "product" not in pd or not isinstance(pd["product"], dict): + pd["product"] = {} + pd["product"]["productKind"] = "Game" + resp.set_text(json.dumps(data, ensure_ascii=False)) + ctx.log.info("[xbox-nodash-v4] Modified productKind -> Game for product endpoint") + except Exception as ex: + ctx.log.warn(f"[xbox-nodash-v4] failed to set productKind: {ex}") + except Exception as e: + ctx.log.warn(f"[xbox-nodash-v4] unexpected in response hook: {e}") + + +addons = [MitmXboxAddonNoDashV4()] \ No newline at end of file diff --git a/mitmproxy/docker-compose.yml b/mitmproxy/docker-compose.yml new file mode 100644 index 0000000..57603ad --- /dev/null +++ b/mitmproxy/docker-compose.yml @@ -0,0 +1,65 @@ +version: "3.8" + +services: + mitmproxy: + image: mitmproxy/mitmproxy:latest + container_name: mitmproxy + restart: unless-stopped + + ports: + - "31280:8080" # HTTP/HTTPS 代理入口 (客户端走这个端口当代理) + - "18081:8081" # mitmweb 控制台(带密码) + + command: + - mitmweb + - "--listen-host" + - "0.0.0.0" + - "--web-host" + - "0.0.0.0" + - "--set" + - "web_password=8130899" + - "--set" + - "block_global=false" # 允许外部客户端接入 + - "--set" + - "connection_strategy=lazy" + - "--set" + - "http2=false" # 关 http2,方便改请求头/请求体 + - "--proxyauth=hbxnlsy:8130899" # 代理验证 用户名:密码 + - "-s" + - "/addons/mitm_xbox_addon.py" # 你的主脚本(含自动发 Telegram 通知) + + volumes: + - /opt/1panel/docker/compose/mitmproxy/addons:/addons + - /opt/1panel/docker/compose/mitmproxy/mitm-data:/home/mitmproxy/.mitmproxy + environment: + - TZ=Asia/Shanghai + logging: + options: + max-size: "10m" + max-file: "3" + + xboxbot: + build: + context: ./xboxbot + dockerfile: Dockerfile + container_name: xboxbot + restart: unless-stopped + + environment: + - TELEGRAM_BOT_TOKEN=8293676109:AAG3f6GnZNxJiwxwyGh_OtTU4wGn6-ypg_4 + - TELEGRAM_ALLOWED_USER_ID=1732587552,7935041828 + - STATE_PATH=/shared/state.json + - MITM_MARKET_LOCALE=en-ng + - MITM_MARKET_CODE=NG + + volumes: + # 让 bot 跟 mitmproxy 共享同一份 state.json + - /opt/1panel/docker/compose/mitmproxy/mitm-data:/shared:rw + + depends_on: + - mitmproxy + + logging: + options: + max-size: "5m" + max-file: "3" \ No newline at end of file diff --git a/mitmproxy/mitm-data/mitmproxy-ca-cert.cer b/mitmproxy/mitm-data/mitmproxy-ca-cert.cer new file mode 100644 index 0000000..206490c --- /dev/null +++ b/mitmproxy/mitm-data/mitmproxy-ca-cert.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUd4CmNZbEMWsQ9UFIrXFJx87zufEwDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjUxMDI1MTIzMzA0WhcNMzUxMDI1MTIzMzA0WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALCVT6FiAvJgHkPt7lgd7ZfhD5lFmjZ/P7OmBloRZ4A/xTBVTV/a7KGJ +J7tscBRrTWihNHIQZcm8sKpCgwLrQc4SCTBlOPHQ8Imh9gLe8r1XBu+nCduScYXy +31R9Lpyh89RqW/384HgVMVKRf8qcv6qikk5+GqxZxtgIBmR0RyU+jPyBSFV0djjx +kdkuKcFF8VNayL2QCXtofBS5/+xk+iLzMBIqU8WIXGEvf2mTq8iM63bXQNVq3Iq/ +zyUgOlAF++XciY6uTu5ln6+tBlJXHwKTcQM75Jk91tjfGDtSAoRPZ0iduZtNO5mh +qHxcUXaqFocPscYKnY4OgfQ3UjlTHjMCAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FNcWV6LaBZxIcAciAhMEreA8Eb+fMA0GCSqGSIb3DQEBCwUAA4IBAQBJNkaTgCJq +RTyv0PQgt2cw96mwGmjdBbeVUrQrTdaQkSIzaekpD/5bidiD0AqZtZT2/H+BeVRW +d+fO8Rp74CipqKpEut/fl7gMbiHWGYtUxmtG+TCe2+c6syW2BEDCt+dD1mYgN7Of +itllDqh/61HhCtEiEo4HzAjSiMhmbt/uT8310pyhmtuRSgsQLyLk4F1Qlp092ObA +cLTjafd6x5TjaYMxaGXEyi7MyvHOAr+UCaotap7xQT7NdQ9ewGYtmyTBUszJ0j1i +ec/hny65gfCDk5wC6556e5YXigF/WEjV5zDykskrx4bdVoqxvJd+F1ITNrBSpGDj +7wetRUpGIcKM +-----END CERTIFICATE----- diff --git a/mitmproxy/mitm-data/mitmproxy-ca-cert.p12 b/mitmproxy/mitm-data/mitmproxy-ca-cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..5b381013a12eaff5336526faad630c8d4433f25f GIT binary patch literal 1035 zcmXqLVqs@uWHxAG-pj_R)#lOmotKfFaX}OFI+iBpRY2ioK)eW5YBEr&A1K@n#O-X{ zP+dG+jLblNfj~iDHZG_jrx?gATc8F@ptz|)6O-%$W+p}^CXw=nWv0`P7-kE6b@W(U z=z09y=bax7c;OZ?a-=4O`U78K=IRKmo#ki-n+#CeTO4GndjgB`ASyw-#-s3L=A%`)}NZQf7PN% zesxl7B9GnRU`r`+SGDW;)94XeQfBdS;!QoxgRUQgqfYFdz*(J9BeL`Vo0MNlpACex zf{%8@B+R{r&V#XWu%%cd7H&uVo7gmuH$>$ZY*& zrtP&G_a&@@m|FbPJ?8G5?Q1=A;fk7=z_L|h?fe^$an0@HYy4s!WEm`H%*4#dz_>Wv zAk=^#7&EfMjEw(TSPhtglz}ivK$V5ZfQyYon~jl`m7ST)$4TdZ?D3fPsHgjv=;uo2upA35lODF7Jsj0~P;Zj&37vRrM}U-+W1J>B5@ z$_-K(cUiYj4cemZdu_r*CF9JOn*9HwJ8v{!;F`I0%C|rDjg=u`<WR^7(paA^B;=WA&S=9}kt-Av_MQU5ydA=gDE zp+5FA9G5yyq~+az=YRI=r8x^{-Ja;hEugRTgA?*sK-#*KoOoV zI7JLaC*{vxO`JUIcnQC6V@$p%V(Zq;kbS&tG|t1bRrWI2Lk{>_<(N! literal 0 HcmV?d00001 diff --git a/mitmproxy/mitm-data/mitmproxy-ca-cert.pem b/mitmproxy/mitm-data/mitmproxy-ca-cert.pem new file mode 100644 index 0000000..206490c --- /dev/null +++ b/mitmproxy/mitm-data/mitmproxy-ca-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUd4CmNZbEMWsQ9UFIrXFJx87zufEwDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjUxMDI1MTIzMzA0WhcNMzUxMDI1MTIzMzA0WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALCVT6FiAvJgHkPt7lgd7ZfhD5lFmjZ/P7OmBloRZ4A/xTBVTV/a7KGJ +J7tscBRrTWihNHIQZcm8sKpCgwLrQc4SCTBlOPHQ8Imh9gLe8r1XBu+nCduScYXy +31R9Lpyh89RqW/384HgVMVKRf8qcv6qikk5+GqxZxtgIBmR0RyU+jPyBSFV0djjx +kdkuKcFF8VNayL2QCXtofBS5/+xk+iLzMBIqU8WIXGEvf2mTq8iM63bXQNVq3Iq/ +zyUgOlAF++XciY6uTu5ln6+tBlJXHwKTcQM75Jk91tjfGDtSAoRPZ0iduZtNO5mh +qHxcUXaqFocPscYKnY4OgfQ3UjlTHjMCAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FNcWV6LaBZxIcAciAhMEreA8Eb+fMA0GCSqGSIb3DQEBCwUAA4IBAQBJNkaTgCJq +RTyv0PQgt2cw96mwGmjdBbeVUrQrTdaQkSIzaekpD/5bidiD0AqZtZT2/H+BeVRW +d+fO8Rp74CipqKpEut/fl7gMbiHWGYtUxmtG+TCe2+c6syW2BEDCt+dD1mYgN7Of +itllDqh/61HhCtEiEo4HzAjSiMhmbt/uT8310pyhmtuRSgsQLyLk4F1Qlp092ObA +cLTjafd6x5TjaYMxaGXEyi7MyvHOAr+UCaotap7xQT7NdQ9ewGYtmyTBUszJ0j1i +ec/hny65gfCDk5wC6556e5YXigF/WEjV5zDykskrx4bdVoqxvJd+F1ITNrBSpGDj +7wetRUpGIcKM +-----END CERTIFICATE----- diff --git a/mitmproxy/mitm-data/mitmproxy-ca.p12 b/mitmproxy/mitm-data/mitmproxy-ca.p12 new file mode 100644 index 0000000000000000000000000000000000000000..fbb2e8f7cf625b68c0d67761429f74dc1bd03538 GIT binary patch literal 2384 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 {code}") + # 从 remain 里删掉成功的 + remain = [ + r for r in remain + if not (r["ProductId"] == pid and r["SkuId"] == sid and r["AvailabilityId"] == aid) + ] + else: + failures.append(f"{triple_label} -> {code or 'ERR'}") + + time.sleep(0.15) + + # 更新 state.json 里的 products / XboxProductList (不推送通知,只本地改) + _save_products_no_notify(remain) + + summary = ( + "🛒 /addtocart 完成\n" + f"尝试: {len(products)}\n" + f"成功: {len(successes)}\n" + f"失败: {len(failures)}\n\n" + ) + if successes: + summary += "✅ 成功:\n" + "\n".join(successes[:20]) + ("\n..." if len(successes) > 20 else "") + "\n" + if failures: + summary += "❌ 失败:\n" + "\n".join(failures[:20]) + ("\n..." if len(failures) > 20 else "") + "\n" + summary += f"\n剩余待加入: {len(remain)}" + + await _reply(update, context, summary) + +# ========== 主入口 ========== +def main(): + app = ApplicationBuilder().token(BOT_TOKEN).build() + + app.add_handler(CommandHandler("start", cmd_start)) + app.add_handler(CommandHandler("help", cmd_help)) + app.add_handler(CommandHandler("check", cmd_check)) + app.add_handler(CommandHandler("viewproducts", cmd_viewproducts)) + app.add_handler(CommandHandler("clearproducts",cmd_clearproducts)) + app.add_handler(CommandHandler("addtocart", cmd_addtocart)) + + # 直接长轮询。通知静默控制在我们send_message里disable_notification=False + app.run_polling(drop_pending_updates=True) + +if __name__ == "__main__": + main() \ No newline at end of file