猫抓 - 深度搜索

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

目前為 2024-11-13 提交的版本,檢視 最新版本

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