Guest User

Untitled

a guest
Jul 26th, 2026
2,713
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.49 KB | None | 0 0
  1. // ==UserScript==
  2. // @name TestExample Cassia Response Mock
  3. // @namespace local.testexample.checkout
  4. // @version 1.1.0
  5. // @description 将 checkout_capabilities 响应改写为 cassia
  6. // @match ://claude.ai/
  7. // @match ://.claude.ai/*
  8. // @run-at document-start
  9. // @grant none
  10. // @sandbox raw
  11. // ==/UserScript==
  12. (function () {
  13. “use strict”;
  14. const TARGET_HOST = “claude.ai”;
  15. const TARGET_PATH =
  16. /^/api/organizations/[^/]+/subscription/checkout_capabilities/?$/;
  17. const MOCK_DATA = {
  18. checkout_flow: “cassia”
  19. };
  20. const MOCK_BODY = JSON.stringify(MOCK_DATA);
  21. const MOCK_LENGTH =
  22. new TextEncoder().encode(MOCK_BODY).byteLength;
  23. function getTargetUrl(input, method = “GET”) {
  24. try {
  25. let rawUrl;
  26. if (typeof input === "string" || input instanceof URL) {
  27. rawUrl = String(input);
  28. } else if (input && typeof input.url === "string") {
  29. rawUrl = input.url;
  30. } else {
  31. return null;
  32. }
  33.  
  34. const url = new URL(rawUrl, location.href);
  35.  
  36. if (String(method).toUpperCase() !== "GET") {
  37. return null;
  38. }
  39.  
  40. // 允许主域名以及其子域名
  41. const hostMatched =
  42. url.hostname === TARGET_HOST ||
  43. url.hostname.endsWith("." + TARGET_HOST);
  44.  
  45. if (!hostMatched) {
  46. return null;
  47. }
  48.  
  49. if (!TARGET_PATH.test(url.pathname)) {
  50. return null;
  51. }
  52.  
  53. return url;
  54. } catch (error) {
  55. console.error("[Cassia Mock] URL 解析失败:", error);
  56. return null;
  57. }
  58. }
  59. function createMockResponse(originalResponse) {
  60. const headers = new Headers(originalResponse.headers);
  61. headers.delete("content-length");
  62. headers.delete("content-encoding");
  63. headers.delete("etag");
  64. headers.delete("content-md5");
  65.  
  66. headers.set(
  67. "content-type",
  68. "application/json; charset=utf-8"
  69. );
  70. headers.set("content-length", String(MOCK_LENGTH));
  71. headers.set("cache-control", "no-store");
  72.  
  73. const response = new Response(MOCK_BODY, {
  74. status: 200,
  75. statusText: "OK",
  76. headers
  77. });
  78.  
  79. // 尽量保留原始响应信息
  80. try {
  81. Object.defineProperties(response, {
  82. url: {
  83. value: originalResponse.url,
  84. configurable: true
  85. },
  86. redirected: {
  87. value: originalResponse.redirected,
  88. configurable: true
  89. },
  90. type: {
  91. value: originalResponse.type,
  92. configurable: true
  93. }
  94. });
  95. } catch (_) {
  96. // 不影响主体改写
  97. }
  98.  
  99. return response;
  100.  
  101. }
  102. /*
  103.  
  104. 拦截 Fetch
  105. */
  106. const nativeFetch = window.fetch;
  107.  
  108. window.fetch = async function (input, init) {
  109. const method =
  110. init?.method ||
  111. (input instanceof Request ? input.method : “GET”);
  112. const targetUrl = getTargetUrl(input, method);
  113.  
  114. const originalResponse =
  115. await nativeFetch.apply(this, arguments);
  116.  
  117. if (!targetUrl) {
  118. return originalResponse;
  119. }
  120.  
  121. console.warn(
  122. "[Cassia Mock] Fetch 响应已改写:",
  123. targetUrl.href,
  124. MOCK_DATA
  125. );
  126.  
  127. return createMockResponse(originalResponse);
  128. };
  129. /*
  130.  
  131. 拦截 XMLHttpRequest
  132. */
  133. const XhrPrototype = XMLHttpRequest.prototype;
  134. const xhrInfo = new WeakMap();
  135. const loggedXhrs = new WeakSet();
  136. const nativeOpen = XhrPrototype.open;
  137. const nativeSend = XhrPrototype.send;
  138. const nativeGetResponseHeader =
  139. XhrPrototype.getResponseHeader;
  140. const nativeGetAllResponseHeaders =
  141. XhrPrototype.getAllResponseHeaders;
  142. XhrPrototype.open = function (method, url) {
  143. let absoluteUrl;
  144.  
  145. try {
  146. absoluteUrl = new URL(
  147. String(url),
  148. location.href
  149. ).href;
  150. } catch (_) {
  151. absoluteUrl = String(url);
  152. }
  153.  
  154. xhrInfo.set(this, {
  155. method: String(method || "GET").toUpperCase(),
  156. url: absoluteUrl
  157. });
  158.  
  159. return nativeOpen.apply(this, arguments);
  160. };
  161. function getMatchedXhr(xhr) {
  162. const info = xhrInfo.get(xhr);
  163.  
  164. if (
  165. !info ||
  166. xhr.readyState !== XMLHttpRequest.DONE
  167. ) {
  168. return null;
  169. }
  170.  
  171. return getTargetUrl(info.url, info.method);
  172. }
  173. function replaceXhrGetter(propertyName, replacement) {
  174. const descriptor =
  175. Object.getOwnPropertyDescriptor(
  176. XhrPrototype,
  177. propertyName
  178. );
  179.  
  180. if (
  181. !descriptor ||
  182. typeof descriptor.get !== "function" ||
  183. descriptor.configurable === false
  184. ) {
  185. console.warn(
  186. `[Cassia Mock] 无法接管 XHR.${propertyName}`
  187. );
  188. return;
  189. }
  190.  
  191. const nativeGetter = descriptor.get;
  192.  
  193. Object.defineProperty(XhrPrototype, propertyName, {
  194. ...descriptor,
  195.  
  196. get: function () {
  197. if (!getMatchedXhr(this)) {
  198. return nativeGetter.call(this);
  199. }
  200.  
  201. return replacement.call(this, nativeGetter);
  202. }
  203. });
  204. }
  205. replaceXhrGetter(
  206. “responseText”,
  207. function (nativeGetter) {
  208. if (
  209. this.responseType !== “” &&
  210. this.responseType !== “text”
  211. ) {
  212. return nativeGetter.call(this);
  213. }
  214. return MOCK_BODY;
  215. }
  216. );
  217. replaceXhrGetter(
  218. “response”,
  219. function (nativeGetter) {
  220. if (this.responseType === “json”) {
  221. return {
  222. checkout_flow: “cassia”
  223. };
  224. }
  225. if (
  226. this.responseType === "" ||
  227. this.responseType === "text"
  228. ) {
  229. return MOCK_BODY;
  230. }
  231.  
  232. return nativeGetter.call(this);
  233. }
  234. );
  235.  
  236. replaceXhrGetter(“status”, function () {
  237. return 200;
  238. });
  239.  
  240. replaceXhrGetter(“statusText”, function () {
  241. return “OK”;
  242. });
  243. XhrPrototype.getResponseHeader = function (name) {
  244. if (!getMatchedXhr(this)) {
  245. return nativeGetResponseHeader.apply(
  246. this,
  247. arguments
  248. );
  249. }
  250. switch (String(name).toLowerCase()) {
  251. case "content-type":
  252. return "application/json; charset=utf-8";
  253.  
  254. case "content-length":
  255. return String(MOCK_LENGTH);
  256.  
  257. case "cache-control":
  258. return "no-store";
  259.  
  260. case "content-encoding":
  261. case "etag":
  262. case "content-md5":
  263. return null;
  264.  
  265. default:
  266. return nativeGetResponseHeader.apply(
  267. this,
  268. arguments
  269. );
  270. }
  271.  
  272. };
  273. XhrPrototype.getAllResponseHeaders = function () {
  274. const originalHeaders =
  275. nativeGetAllResponseHeaders.apply(this, arguments);
  276. if (!getMatchedXhr(this)) {
  277. return originalHeaders;
  278. }
  279.  
  280. const headers = String(originalHeaders || "")
  281. .split(/\r?\n/)
  282. .filter(Boolean)
  283. .filter(function (line) {
  284. const name = line
  285. .split(":", 1)[0]
  286. .trim()
  287. .toLowerCase();
  288.  
  289. return ![
  290. "content-type",
  291. "content-length",
  292. "content-encoding",
  293. "cache-control",
  294. "etag",
  295. "content-md5"
  296. ].includes(name);
  297. });
  298.  
  299. headers.push(
  300. "content-type: application/json; charset=utf-8",
  301. `content-length: ${MOCK_LENGTH}`,
  302. "cache-control: no-store"
  303. );
  304.  
  305. return headers.join("\r\n") + "\r\n";
  306. };
  307.  
  308. XhrPrototype.send = function () {
  309. this.addEventListener(
  310. “readystatechange”,
  311. function () {
  312. const targetUrl = getMatchedXhr(this);
  313. if (targetUrl && !loggedXhrs.has(this)) {
  314. loggedXhrs.add(this);
  315.  
  316. console.warn(
  317. "[Cassia Mock] XHR 响应已改写:",
  318. targetUrl.href,
  319. MOCK_DATA
  320. );
  321. }
  322. }
  323. );
  324.  
  325. return nativeSend.apply(this, arguments);
  326. };
  327.  
  328. /*
  329. 显示运行标记
  330. */
  331. function showStatusBadge() {
  332. if (!document.documentElement) {
  333. document.addEventListener(
  334. “DOMContentLoaded”,
  335. showStatusBadge,
  336. { once: true }
  337. );
  338. return;
  339. }
  340. if (document.getElementById("cassia-mock-badge")) {
  341. return;
  342. }
  343.  
  344. const badge = document.createElement("div");
  345. badge.id = "cassia-mock-badge";
  346. badge.textContent = "Cassia Mock ON";
  347.  
  348. Object.assign(badge.style, {
  349. position: "fixed",
  350. right: "12px",
  351. bottom: "12px",
  352. zIndex: "2147483647",
  353. padding: "7px 11px",
  354. color: "#ffffff",
  355. background: "#167c3a",
  356. borderRadius: "6px",
  357. fontSize: "12px",
  358. fontFamily: "sans-serif",
  359. boxShadow: "0 2px 8px rgba(0,0,0,.3)"
  360. });
  361.  
  362. document.documentElement.appendChild(badge);
  363.  
  364. }
  365. window.__cassiaMockInstalled = true;
  366. console.info(
  367. “[Cassia Mock] 脚本已加载:”,
  368. location.href
  369. );
  370. showStatusBadge();
  371. })();
Advertisement
Add Comment
Please, Sign In to add comment