Greasy Fork is available in English.

猫抓 - 深度搜索

猫抓扩展提取出来的深度搜索脚本。

Per 04-08-2024. Zie de nieuwste versie.

  1. // ==UserScript==
  2. // @name 猫抓 - 深度搜索
  3. // @namespace https://bmmmd.com
  4. // @version 2.5.4.1
  5. // @description 猫抓扩展提取出来的深度搜索脚本。
  6. // @author bmm
  7. // @match http://*/*
  8. // @match https://*/*
  9. // @exclude https://ffmpeg.bmmmd.com/
  10. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  11. // @grant none
  12. // @run-at document-start
  13. // @license GPL v3
  14. // ==/UserScript==
  15.  
  16. window.Worker = undefined;
  17.  
  18. // const CATCH_SEARCH_ONLY = true;
  19. (function () {
  20. const CATCH_SEARCH_DEBUG = false;
  21. // 防止 console.log 被劫持
  22. if (CATCH_SEARCH_DEBUG && console.log.toString() != 'function log() { [native code] }') {
  23. const newIframe = top.document.createElement("iframe");
  24. newIframe.style.width = 0;
  25. newIframe.style.height = 0;
  26. top.document.body.appendChild(newIframe);
  27. newIframe.contentwindow.document.write("<script>(window.catCatchLOG=function(){console.log(...arguments);})();</script>");
  28. window.console.log = newIframe.contentwindow.catCatchLOG;
  29. }
  30. // 防止 window.postMessage 被劫持
  31. const _postMessage = window.postMessage;
  32.  
  33. console.log("start search.js");
  34. const filter = new Set();
  35. const reKeyURL = /URI="(.*)"/;
  36.  
  37. // JSON.parse
  38. const _JSONparse = JSON.parse;
  39. JSON.parse = function () {
  40. let data = _JSONparse.apply(this, arguments);
  41. findMedia(data);
  42. return data;
  43. }
  44. JSON.parse.toString = function () {
  45. return _JSONparse.toString();
  46. }
  47.  
  48. async function findMedia(data, depth = 0) {
  49. CATCH_SEARCH_DEBUG && console.log(data);
  50. let index = 0;
  51. if (!data) { return; }
  52. if (data instanceof Array && data.length == 16) {
  53. const isKey = data.every(function (value) {
  54. return typeof value == 'number' && value <= 256
  55. });
  56. if (isKey) {
  57. postData({ action: "catCatchAddKey", key: data, href: location.href, ext: "key" });
  58. return;
  59. }
  60. }
  61. for (let key in data) {
  62. if (index != 0) { depth = 0; } index++;
  63. if (typeof data[key] == "object") {
  64. // 查找疑似key
  65. if (data[key] instanceof Array && data[key].length == 16) {
  66. const isKey = data[key].every(function (value) {
  67. return typeof value == 'number' && value <= 256
  68. });
  69. isKey && postData({ action: "catCatchAddKey", key: data[key], href: location.href, ext: "key" });
  70. continue;
  71. }
  72. if (depth > 10) { continue; } // 防止死循环 最大深度
  73. findMedia(data[key], ++depth);
  74. continue;
  75. }
  76. if (typeof data[key] == "string") {
  77. if (isUrl(data[key])) {
  78. let ext = getExtension(data[key]);
  79. ext && postData({ action: "catCatchAddMedia", url: data[key], href: location.href, ext: ext });
  80. continue;
  81. }
  82. if (data[key].substring(0, 7).toUpperCase() == "#EXTM3U") {
  83. isFullM3u8(data[key]) && toUrl(data[key]);
  84. continue;
  85. }
  86. if (data[key].substring(0, 17).toLowerCase() == "data:application/") {
  87. const text = getDataM3U8(data[key].substring(17));
  88. text && toUrl(text);
  89. continue;
  90. }
  91. if (data[key].toLowerCase().includes("urn:mpeg:dash:schema:mpd")) {
  92. toUrl(data[key], "mpd");
  93. continue;
  94. }
  95. if (CATCH_SEARCH_DEBUG && data[key].includes("manifest")) {
  96. console.log(data);
  97. }
  98. }
  99. }
  100. }
  101.  
  102. // XHR
  103. const _xhrOpen = XMLHttpRequest.prototype.open;
  104. XMLHttpRequest.prototype.open = function (method) {
  105. method = method.toUpperCase();
  106. CATCH_SEARCH_DEBUG && console.log(this);
  107. this.addEventListener("readystatechange", function (event) {
  108. CATCH_SEARCH_DEBUG && console.log(this);
  109. if (this.status != 200) { return; }
  110. // 查找疑似key
  111. if (this.responseType == "arraybuffer" && this.response?.byteLength && this.response.byteLength == 32) {
  112. console.log(this.response);
  113. }
  114. if (this.responseType == "arraybuffer" && this.response?.byteLength && this.response.byteLength == 16) {
  115. postData({ action: "catCatchAddKey", key: this.response, href: location.href, ext: "key" });
  116. }
  117. if (typeof this.response == "object") {
  118. findMedia(this.response);
  119. return;
  120. }
  121. if (this.response == "" || typeof this.response != "string") { return; }
  122. if (this.response.substring(0, 17).toLowerCase() == "data:application/") {
  123. const text = getDataM3U8(this.response.substring(17));
  124. text && toUrl(text);
  125. return;
  126. }
  127. if (this.responseURL.substring(0, 17).toLowerCase() == "data:application/") {
  128. const text = getDataM3U8(this.responseURL.substring(17));
  129. text && toUrl(text);
  130. return;
  131. }
  132. if (isUrl(this.response)) {
  133. const ext = getExtension(this.response);
  134. ext && postData({ action: "catCatchAddMedia", url: this.response, href: location.href, ext: ext });
  135. return;
  136. }
  137. if (this.response.toUpperCase().includes("#EXTM3U")) {
  138. if (this.response.substring(0, 7) == "#EXTM3U") {
  139. if (method == "GET") {
  140. toUrl(addBashUrl(getBashUrl(this.responseURL), this.response));
  141. postData({ action: "catCatchAddMedia", url: this.responseURL, href: location.href, ext: "m3u8" });
  142. return;
  143. }
  144. isFullM3u8(this.response) && toUrl(this.response);
  145. return;
  146. }
  147. if (isJSON(this.response)) {
  148. if (method == "GET") {
  149. postData({ action: "catCatchAddMedia", url: this.responseURL, href: location.href, ext: "json" });
  150. return;
  151. }
  152. toUrl(this.response, "json");
  153. return;
  154. }
  155. }
  156. const isJson = isJSON(this.response);
  157. if (isJson) {
  158. findMedia(isJson);
  159. return;
  160. }
  161. });
  162. _xhrOpen.apply(this, arguments);
  163. }
  164. XMLHttpRequest.prototype.open.toString = function () {
  165. return _xhrOpen.toString();
  166. }
  167.  
  168. // fetch
  169. const _fetch = window.fetch;
  170. window.fetch = async function (input, init) {
  171. const response = await _fetch.apply(this, arguments);
  172. const clone = response.clone();
  173. CATCH_SEARCH_DEBUG && console.log(response);
  174. response.arrayBuffer()
  175. .then(arrayBuffer => {
  176. CATCH_SEARCH_DEBUG && console.log({ arrayBuffer, input });
  177. if (arrayBuffer.byteLength == 16) {
  178. postData({ action: "catCatchAddKey", key: arrayBuffer, href: location.href, ext: "key" });
  179. return;
  180. }
  181. let text = new TextDecoder().decode(arrayBuffer);
  182. if (text == "") { return; }
  183. if (typeof input == "object") { input = input.url; }
  184. let isJson = isJSON(text);
  185. if (isJson) {
  186. findMedia(isJson);
  187. return;
  188. }
  189. if (text.substring(0, 7).toUpperCase() == "#EXTM3U") {
  190. if (init?.method == undefined || (init.method && init.method.toUpperCase() == "GET")) {
  191. toUrl(addBashUrl(getBashUrl(input), text));
  192. postData({ action: "catCatchAddMedia", url: input, href: location.href, ext: "m3u8" });
  193. return;
  194. }
  195. isFullM3u8(text) && toUrl(text);
  196. return;
  197. }
  198. if (text.substring(0, 17).toLowerCase() == "data:application/") {
  199. const text = getDataM3U8(text.substring(0, 17));
  200. text && toUrl(text);
  201. return;
  202. }
  203. });
  204. return clone;
  205. }
  206. window.fetch.toString = function () {
  207. return _fetch.toString();
  208. }
  209.  
  210. // Array.prototype.slice
  211. const _slice = Array.prototype.slice;
  212. Array.prototype.slice = function (start, end) {
  213. const data = _slice.apply(this, arguments);
  214. if (end == 16 && this.length == 32) {
  215. for (let item of data) {
  216. if (typeof item != "number" || item > 255) { return data; }
  217. }
  218. postData({ action: "catCatchAddKey", key: data, href: location.href, ext: "key" });
  219. }
  220. return data;
  221. }
  222. Array.prototype.slice.toString = function () {
  223. return _slice.toString();
  224. }
  225.  
  226. // Int8Array.prototype.subarray
  227. const _subarray = Int8Array.prototype.subarray;
  228. Int8Array.prototype.subarray = function (start, end) {
  229. const data = _subarray.apply(this, arguments);
  230. if (data.byteLength == 16) {
  231. const uint8 = new _Uint8Array(data);
  232. for (let item of uint8) {
  233. if (typeof item != "number" || item > 255) { return data; }
  234. }
  235. postData({ action: "catCatchAddKey", key: uint8.buffer, href: location.href, ext: "key" });
  236. }
  237. return data;
  238. }
  239. Int8Array.prototype.subarray.toString = function () {
  240. return _subarray.toString();
  241. }
  242.  
  243. // window.btoa / window.atob
  244. const _btoa = window.btoa;
  245. window.btoa = function (data) {
  246. const base64 = _btoa.apply(this, arguments);
  247. CATCH_SEARCH_DEBUG && console.log(base64, data, base64.length);
  248. if (base64.length == 24 && base64.substring(22, 24) == "==") {
  249. postData({ action: "catCatchAddKey", key: base64, href: location.href, ext: "base64Key" });
  250. }
  251. if (data.substring(0, 7).toUpperCase() == "#EXTM3U" && isFullM3u8(data)) {
  252. toUrl(data);
  253. }
  254. return base64;
  255. }
  256. window.btoa.toString = function () {
  257. return _btoa.toString();
  258. }
  259. const _atob = window.atob;
  260. window.atob = function (base64) {
  261. const data = _atob.apply(this, arguments);
  262. CATCH_SEARCH_DEBUG && console.log(base64, data, base64.length);
  263. if (base64.length == 24 && base64.substring(22, 24) == "==") {
  264. postData({ action: "catCatchAddKey", key: base64, href: location.href, ext: "base64Key" });
  265. }
  266. if (data.substring(0, 7).toUpperCase() == "#EXTM3U" && isFullM3u8(data)) {
  267. toUrl(data);
  268. }
  269. if (data.endsWith("</MPD>")) {
  270. toUrl(data, "mpd");
  271. }
  272. return data;
  273. }
  274. window.atob.toString = function () {
  275. return _atob.toString();
  276. }
  277.  
  278. // fromCharCode
  279. const _fromCharCode = String.fromCharCode;
  280. let m3u8Text = '';
  281. String.fromCharCode = function () {
  282. const data = _fromCharCode.apply(this, arguments);
  283. if (data.length < 7) { return data; }
  284. if (data.substring(0, 7) == "#EXTM3U" || data.includes("#EXTINF:")) {
  285. m3u8Text += data;
  286. if (m3u8Text.includes("#EXT-X-ENDLIST")) {
  287. toUrl(m3u8Text.split("#EXT-X-ENDLIST")[0] + "#EXT-X-ENDLIST");
  288. m3u8Text = '';
  289. }
  290. return data;
  291. }
  292. const key = data.replaceAll("\u0010", "");
  293. if (key.length == 32) {
  294. postData({ action: "catCatchAddKey", key: key, href: location.href, ext: "key" });
  295. }
  296. return data;
  297. }
  298. String.fromCharCode.toString = function () {
  299. return _fromCharCode.toString();
  300. }
  301.  
  302. // DataView
  303. const _DataView = DataView;
  304. window.DataView = new Proxy(_DataView, {
  305. construct(target, args) {
  306. let instance = new target(...args);
  307. instance.setInt32 = new Proxy(instance.setInt32, {
  308. apply(target, thisArg, argArray) {
  309. Reflect.apply(target, thisArg, argArray);
  310. if (thisArg.byteLength == 16) {
  311. postData({ action: "catCatchAddKey", key: thisArg.buffer, href: location.href, ext: "key" });
  312. }
  313. return;
  314. }
  315. });
  316. if (instance.byteLength == 16 && instance.buffer.byteLength == 16) {
  317. postData({ action: "catCatchAddKey", key: instance.buffer, href: location.href, ext: "key" });
  318. }
  319. if (instance.byteLength == 256 || instance.byteLength == 128) {
  320. const _buffer = isRepeatedExpansion(instance.buffer, 16);
  321. if (_buffer) {
  322. postData({ action: "catCatchAddKey", key: _buffer, href: location.href, ext: "key" });
  323. }
  324. }
  325. return instance;
  326. }
  327. });
  328.  
  329. // escape
  330. const _escape = window.escape;
  331. window.escape = function (str) {
  332. if (str?.length && str.length == 24 && str.substring(22, 24) == "==") {
  333. postData({ action: "catCatchAddKey", key: str, href: location.href, ext: "base64Key" });
  334. }
  335. return _escape(str);
  336. }
  337. escape.toString = function () {
  338. return _escape.toString();
  339. }
  340.  
  341. // findTypedArray
  342. const findTypedArray = (target, args) => {
  343. const isArray = Array.isArray(args[0]) && args[0].length === 16;
  344. const isArrayBuffer = args[0] instanceof ArrayBuffer && args[0].byteLength === 16;
  345. const instance = new target(...args);
  346. if (isArray || isArrayBuffer) {
  347. postData({ action: "catCatchAddKey", key: args[0], href: location.href, ext: "key" });
  348. } else if (instance.buffer.byteLength === 16) {
  349. postData({ action: "catCatchAddKey", key: instance.buffer, href: location.href, ext: "key" });
  350. }
  351. return instance;
  352. }
  353. // Uint8Array
  354. const _Uint8Array = Uint8Array;
  355. window.Uint8Array = new Proxy(_Uint8Array, {
  356. construct(target, args) {
  357. return findTypedArray(target, args);
  358. }
  359. });
  360. // Uint16Array
  361. const _Uint16Array = Uint16Array;
  362. window.Uint16Array = new Proxy(_Uint16Array, {
  363. construct(target, args) {
  364. return findTypedArray(target, args);
  365. }
  366. });
  367. // Uint32Array
  368. const _Uint32Array = Uint32Array;
  369. window.Uint32Array = new Proxy(_Uint32Array, {
  370. construct(target, args) {
  371. return findTypedArray(target, args);
  372. }
  373. });
  374.  
  375. // Array join
  376. const _arrayJoin = Array.prototype.join;
  377. Array.prototype.join = function () {
  378. const data = _arrayJoin.apply(this, arguments);
  379. if (data.substring(0, 7).toUpperCase() == "#EXTM3U") {
  380. isFullM3u8(data) && toUrl(data);
  381. }
  382. return data;
  383. }
  384. Array.prototype.join.toString = function () {
  385. return _arrayJoin.toString();
  386. }
  387.  
  388. function isUrl(str) {
  389. return (str.startsWith("http://") || str.startsWith("https://"));
  390. }
  391. function isFullM3u8(text) {
  392. let tsLists = text.split("\n");
  393. for (let ts of tsLists) {
  394. if (ts[0] == "#") { continue; }
  395. if (isUrl(ts)) { return true; }
  396. return false;
  397. }
  398. return false;
  399. }
  400. function getBashUrl(url) {
  401. let bashUrl = url.split("/");
  402. bashUrl.pop();
  403. return bashUrl.join("/") + "/";
  404. }
  405. function addBashUrl(baseUrl, m3u8Text) {
  406. let m3u8_split = m3u8Text.split("\n");
  407. m3u8Text = "";
  408. for (let ts of m3u8_split) {
  409. if (ts == "" || ts == " " || ts == "\n") { continue; }
  410. if (ts.includes("URI=")) {
  411. let KeyURL = reKeyURL.exec(ts);
  412. if (KeyURL && KeyURL[1] && !isUrl(KeyURL[1])) {
  413. ts = ts.replace(reKeyURL, 'URI="' + baseUrl + KeyURL[1] + '"');
  414. }
  415. }
  416. if (ts[0] != "#" && !isUrl(ts)) {
  417. ts = baseUrl + ts;
  418. }
  419. m3u8Text += ts + "\n";
  420. }
  421. return m3u8Text;
  422. }
  423. function isJSON(str) {
  424. if (typeof str == "object") {
  425. return str;
  426. }
  427. if (typeof str == "string") {
  428. try {
  429. return _JSONparse(str);
  430. } catch (e) { return false; }
  431. }
  432. return false;
  433. }
  434. function getExtension(str) {
  435. let ext;
  436. try { ext = new URL(str); } catch (e) { return undefined; }
  437. ext = ext.pathname.split(".");
  438. if (ext.length == 1) { return undefined; }
  439. ext = ext[ext.length - 1].toLowerCase();
  440. if (ext == "m3u8" ||
  441. ext == "m3u" ||
  442. ext == "mpd" ||
  443. ext == "mp4" ||
  444. ext == "mp3" ||
  445. ext == "flv" ||
  446. ext == "key"
  447. ) { return ext; }
  448. return false;
  449. }
  450. function toUrl(text, ext = "m3u8") {
  451. let url = URL.createObjectURL(new Blob([new TextEncoder("utf-8").encode(text)]));
  452. postData({ action: "catCatchAddMedia", url: url, href: location.href, ext: ext });
  453. }
  454. function getDataM3U8(text) {
  455. const type = ["vnd.apple.mpegurl", "x-mpegurl", "mpegurl"];
  456. let isM3U8 = false;
  457. for (let item of type) {
  458. if (text.substring(0, item.length).toLowerCase() == item) {
  459. text = text.substring(item.length + 1);
  460. isM3U8 = true;
  461. break;
  462. }
  463. }
  464. if (!isM3U8) { return false; }
  465. if (text.substring(0, 7).toLowerCase() == "base64,") {
  466. return _atob(text.substring(7));
  467. }
  468. return text;
  469. }
  470. function postData(data) {
  471. if (data.action == "catCatchAddKey") {
  472. if (data.key == "AAAAAAAAAAAAAAAAAAAAAA==") { return; }
  473. if (data.key instanceof ArrayBuffer && isArrayBufferAllZero(data.key)) { return; }
  474. }
  475. let value = data.url ? data.url : data.key;
  476. if (value instanceof ArrayBuffer || value instanceof Array) {
  477. if (value.byteLength == 0) { return; }
  478. data.key = ArrayToBase64(value);
  479. value = data.key;
  480. }
  481. if (filter.has(value)) { return false; }
  482. filter.add(value);
  483. data.requestId = Date.now().toString() + filter.size;
  484. _postMessage(data);
  485. }
  486. function ArrayToBase64(data) {
  487. try {
  488. let bytes = new _Uint8Array(data);
  489. let binary = "";
  490. for (let i = 0; i < bytes.byteLength; i++) {
  491. binary += _fromCharCode(bytes[i]);
  492. }
  493. if (typeof _btoa == "function") {
  494. return _btoa(binary);
  495. }
  496. return _btoa(binary);
  497. } catch (e) {
  498. return false;
  499. }
  500. }
  501. function isRepeatedExpansion(array, expansionLength) {
  502. let _buffer = new _Uint8Array(expansionLength);
  503. array = new _Uint8Array(array);
  504. for (let i = 0; i < expansionLength; i++) {
  505. _buffer[i] = array[i];
  506. for (let j = i + expansionLength; j < array.byteLength; j += expansionLength) {
  507. if (array[i] !== array[j]) {
  508. return false;
  509. }
  510. }
  511. }
  512. return _buffer.buffer;
  513. }
  514. function isArrayBufferAllZero(buffer) {
  515. let view = new _Uint8Array(buffer);
  516. for (let i = 0; i < view.length; i++) {
  517. if (view[i] !== 0) {
  518. return false;
  519. }
  520. }
  521. return true;
  522. }
  523. })();