91-plus

多线程下载91porn视频,跳过广告,清除页面广告。

  1. // ==UserScript==
  2. // @name 91-plus
  3. // @namespace jiuyi
  4. // @version 17
  5. // @author uh58fg
  6. // @description 多线程下载91porn视频,跳过广告,清除页面广告。
  7. // @license WTFPL
  8. // @icon https://img-blog.csdnimg.cn/20181221195058594.gif
  9. // @match *91porn.com/view_video.php?*
  10. // @match *91porn.com/index.php
  11. // @require https://cdn.bootcdn.net/ajax/libs/hls.js/8.0.0-beta.3/hls.min.js
  12. // @require https://cdn.bootcdn.net/ajax/libs/dplayer/1.26.0/DPlayer.min.js
  13. // @require https://greasyfork.org/scripts/466106-91-plus-mux-mp4/code/91-plus-mux-mp4.js?version=1189391
  14. // @require https://cdn.bootcdn.net/ajax/libs/vue/3.2.47/vue.runtime.global.prod.min.js
  15. // @grant window.close
  16. // ==/UserScript==
  17.  
  18. (t=>{const e=document.createElement("style");e.dataset.source="vite-plugin-monkey",e.textContent=t,document.head.append(e)})(' .row2[data-v-56d4409a]{display:flex;justify-content:flex-end}.big-btn[data-v-56d4409a]{position:relative;display:inline-flex;justify-content:center;align-items:center;margin-left:1rem;font-size:1.6rem;min-width:10rem;color:#fff;cursor:pointer;border-radius:4px;border:1px solid #eeeeee;background-color:#3d8ac7;opacity:1;transition:.3s all;padding:.5rem 1rem}.big-btn[data-v-56d4409a]:hover{opacity:.9}.big-btn.disable[data-v-56d4409a]{cursor:not-allowed;background-color:#ddd;color:#000}.m-p-input-container[data-v-56d4409a]{display:flex}.m-p-input-container input[data-v-56d4409a]{flex:1;display:block;padding:0 1rem;font-size:1.8rem;border-radius:4px;box-shadow:none;color:#fff;background:rgba(239,239,239,.3);border:1px solid #cccccc}.m-p-tips[data-v-56d4409a]{width:100%;color:#999;text-align:left;font-style:italic;word-break:break-all}.m-p-tips p[data-v-56d4409a]{width:100px;display:inline-block}.m-p-tips.error-tips[data-v-56d4409a]{color:#dc5350}.m-p-segment[data-v-56d4409a]{text-align:left}.m-p-segment .item[data-v-56d4409a]{display:inline-block;margin:10px 6px;width:50px;height:40px;color:#fff;line-height:40px;text-align:center;border-radius:4px;cursor:help;border:solid 1px #eeeeee;background-color:#ddd;transition:.3s all}.m-p-segment .finish[data-v-56d4409a]{background-color:#0acd76}.m-p-segment .error[data-v-56d4409a]{cursor:pointer;background-color:#dc5350}.m-p-segment .error[data-v-56d4409a]:hover{opacity:.9}.error-btns[data-v-56d4409a]{display:flex;justify-content:flex-end;gap:1rem}.m-p-force[data-v-56d4409a],.m-p-retry[data-v-56d4409a]{margin-top:1rem;right:50px;display:inline-block;padding:6px 12px;font-size:18px;color:#fff;cursor:pointer;border-radius:4px;border:1px solid #eeeeee;background-color:#3d8ac7;opacity:1;transition:.3s all}.m-p-retry[data-v-56d4409a]{right:250px}.m-p-force[data-v-56d4409a]:hover,.m-p-retry[data-v-56d4409a]:hover{opacity:.9}.info[data-v-56d4409a]{display:flex;justify-content:center;align-items:center;gap:1rem;margin-bottom:1rem}.info .options[data-v-56d4409a]{margin-right:1rem;display:flex;flex-direction:column;align-items:flex-end}.info .option[data-v-56d4409a]{cursor:pointer;display:flex;justify-content:center;align-items:center;color:#fff;margin-bottom:.3rem}.info .option label[data-v-56d4409a]{margin:0}.info .option input[data-v-56d4409a]{margin:0;margin-left:1rem;width:1.4rem;height:1.4rem}.progress[data-v-56d4409a]{flex:1;border-radius:.8rem;opacity:1;height:1.5rem;background:#FCFDFF;box-sizing:border-box;border:.1rem solid #FFFFFF;box-shadow:inset 0 -1rem 4rem #fffc,inset 0 1rem 4rem #2d4c7266;display:flex;align-items:center;justify-content:space-between;position:relative}.progress .bar[data-v-56d4409a]{height:100%;width:50%;left:0;top:0;position:absolute;background:linear-gradient(90deg,#9CDCFF 0%,#0ACD76 100%);border-radius:.8rem}.row2[data-v-429419b2]{display:flex;justify-content:flex-end}.big-btn[data-v-429419b2]{position:relative;display:inline-flex;justify-content:center;align-items:center;margin-left:1rem;font-size:1.6rem;min-width:10rem;color:#fff;cursor:pointer;border-radius:4px;border:1px solid #eeeeee;background-color:#3d8ac7;opacity:1;transition:.3s all;padding:.5rem 1rem}.big-btn[data-v-429419b2]:hover{opacity:.9}.big-btn.disable[data-v-429419b2]{cursor:not-allowed;background-color:#ddd;color:#000}.m-p-input-container[data-v-429419b2]{display:flex}.m-p-input-container input[data-v-429419b2]{flex:1;display:block;padding:0 1rem;font-size:1.8rem;border-radius:4px;box-shadow:none;color:#fff;background:rgba(239,239,239,.3);border:1px solid #cccccc}.m-p-tips[data-v-429419b2]{width:100%;color:#999;text-align:left;font-style:italic;word-break:break-all}.m-p-tips p[data-v-429419b2]{width:100px;display:inline-block}.m-p-tips.error-tips[data-v-429419b2]{color:#dc5350}.m-p-segment[data-v-429419b2]{text-align:left}.m-p-segment .item[data-v-429419b2]{display:inline-block;margin:10px 6px;width:50px;height:40px;color:#fff;line-height:40px;text-align:center;border-radius:4px;cursor:help;border:solid 1px #eeeeee;background-color:#ddd;transition:.3s all}.m-p-segment .finish[data-v-429419b2]{background-color:#0acd76}.m-p-segment .error[data-v-429419b2]{cursor:pointer;background-color:#dc5350}.m-p-segment .error[data-v-429419b2]:hover{opacity:.9}.error-btns[data-v-429419b2]{display:flex;justify-content:flex-end;gap:1rem}.m-p-force[data-v-429419b2],.m-p-retry[data-v-429419b2]{margin-top:1rem;right:50px;display:inline-block;padding:6px 12px;font-size:18px;color:#fff;cursor:pointer;border-radius:4px;border:1px solid #eeeeee;background-color:#3d8ac7;opacity:1;transition:.3s all}.m-p-retry[data-v-429419b2]{right:250px}.m-p-force[data-v-429419b2]:hover,.m-p-retry[data-v-429419b2]:hover{opacity:.9}.info[data-v-429419b2]{display:flex;justify-content:center;align-items:center;gap:1rem;margin-bottom:1rem}.info .options[data-v-429419b2]{margin-right:1rem;display:flex;flex-direction:column;align-items:flex-end}.info .option[data-v-429419b2]{cursor:pointer;display:flex;justify-content:center;align-items:center;color:#fff;margin-bottom:.3rem}.info .option label[data-v-429419b2]{margin:0}.info .option input[data-v-429419b2]{margin:0;margin-left:1rem;width:1.4rem;height:1.4rem}.progress[data-v-429419b2]{flex:1;border-radius:.8rem;opacity:1;height:1.5rem;background:#FCFDFF;box-sizing:border-box;border:.1rem solid #FFFFFF;box-shadow:inset 0 -1rem 4rem #fffc,inset 0 1rem 4rem #2d4c7266;display:flex;align-items:center;justify-content:space-between;position:relative}.progress .bar[data-v-429419b2]{height:100%;width:50%;left:0;top:0;position:absolute;background:linear-gradient(90deg,#9CDCFF 0%,#0ACD76 100%);border-radius:.8rem}[data-v-a069d05a]::-webkit-scrollbar{width:10px}[data-v-a069d05a]::-webkit-scrollbar-thumb{background:#4e4e4e;border-radius:25px}.showBtn[data-v-a069d05a]{cursor:pointer;position:fixed;top:1rem;right:1rem;color:#fff;z-index:99999;background:gray;padding:.6rem 1rem;border-radius:.3rem}.content[data-v-a069d05a]{z-index:9999;background:#0f0f0f;position:fixed;left:0;top:0;width:100vw;min-width:100vw;height:100vh;box-sizing:border-box;padding:1rem;display:flex;gap:2rem;text-align:start}.content .close[data-v-a069d05a]{cursor:pointer;position:absolute;top:1rem;right:2rem;width:2rem;height:2rem;line-height:4rem;text-align:center;color:#fff}.content .close[data-v-a069d05a]:before{position:absolute;content:"";width:.2rem;height:2rem;background:white;transform:rotate(45deg);top:calc(50% - .45rem);left:50%}.content .close[data-v-a069d05a]:after{content:"";position:absolute;width:.2rem;height:2rem;background:white;transform:rotate(-45deg);top:calc(50% - .45rem);left:50%}.content .home-icon[data-v-a069d05a]{cursor:pointer;right:2rem;position:absolute}.content .big-title[data-v-a069d05a]{position:relative;display:flex;align-items:center;justify-content:center;height:5rem;font-weight:700;color:#fff;letter-spacing:2px;background:#212121;font-size:2rem}.content .video[data-v-a069d05a]{width:60%;overflow:auto;padding-bottom:2rem}.content .video #dplayer[data-v-a069d05a]{width:100%}.content .video .title[data-v-a069d05a]{margin-top:1rem;font-weight:700;font-size:2rem;color:#fff;margin-bottom:1rem}.content .video .author[data-v-a069d05a]{margin-right:2rem;font-weight:700;font-size:1.4rem;margin-bottom:2rem;display:inline-block}.content .left[data-v-a069d05a]{flex:1;border:1px solid gray;border-radius:.5rem;overflow:hidden;position:relative}.content .left .comments[data-v-a069d05a]{color:#fff;height:calc(100% - 6rem);padding-bottom:4rem;overflow:auto}.content .left .comments .item[data-v-a069d05a]{margin-bottom:5px;padding:10px;border-bottom:1px solid #3f3f3f;text-align:start}.content .left .comments .item .title[data-v-a069d05a]{font-size:1rem}.content .left .comments .item .title span[data-v-a069d05a]{margin-right:10px}.content .left .comments .item .quote[data-v-a069d05a]{margin-top:.5rem;font-size:1rem;padding:.5rem .5rem .5rem 1rem;border:1px dashed gray}.content .left .comments .item .replay[data-v-a069d05a]{margin-top:1rem}.content .right[data-v-a069d05a]{flex:1;border:1px solid gray;border-radius:.5rem;overflow:hidden}.content .right .list[data-v-a069d05a]{color:#fff;height:calc(100% - 6rem);padding:1rem 1rem 4rem;overflow:auto}.ml20[data-v-a069d05a]{margin-left:20px} ');
  19.  
  20. (function (vue) {
  21. 'use strict';
  22.  
  23. var _monkeyWindow = /* @__PURE__ */ (() => window)();
  24. const _export_sfc = (sfc, props) => {
  25. const target = sfc.__vccOpts || sfc;
  26. for (const [key, val] of props) {
  27. target[key] = val;
  28. }
  29. return target;
  30. };
  31. function removePadding(buffer) {
  32. const outputBytes = buffer.byteLength;
  33. const paddingBytes = outputBytes && new DataView(buffer).getUint8(outputBytes - 1);
  34. if (paddingBytes) {
  35. return buffer.slice(0, outputBytes - paddingBytes);
  36. } else {
  37. return buffer;
  38. }
  39. }
  40. function AESDecryptor() {
  41. return {
  42. constructor() {
  43. this.rcon = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54];
  44. this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
  45. this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
  46. this.sBox = new Uint32Array(256);
  47. this.invSBox = new Uint32Array(256);
  48. this.key = new Uint32Array(0);
  49. this.initTable();
  50. },
  51. // Using view.getUint32() also swaps the byte order.
  52. uint8ArrayToUint32Array_(arrayBuffer) {
  53. let view = new DataView(arrayBuffer);
  54. let newArray = new Uint32Array(4);
  55. for (let i = 0; i < 4; i++) {
  56. newArray[i] = view.getUint32(i * 4);
  57. }
  58. return newArray;
  59. },
  60. initTable() {
  61. let sBox = this.sBox;
  62. let invSBox = this.invSBox;
  63. let subMix = this.subMix;
  64. let subMix0 = subMix[0];
  65. let subMix1 = subMix[1];
  66. let subMix2 = subMix[2];
  67. let subMix3 = subMix[3];
  68. let invSubMix = this.invSubMix;
  69. let invSubMix0 = invSubMix[0];
  70. let invSubMix1 = invSubMix[1];
  71. let invSubMix2 = invSubMix[2];
  72. let invSubMix3 = invSubMix[3];
  73. let d = new Uint32Array(256);
  74. let x = 0;
  75. let xi = 0;
  76. let i = 0;
  77. for (i = 0; i < 256; i++) {
  78. if (i < 128) {
  79. d[i] = i << 1;
  80. } else {
  81. d[i] = i << 1 ^ 283;
  82. }
  83. }
  84. for (i = 0; i < 256; i++) {
  85. let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;
  86. sx = sx >>> 8 ^ sx & 255 ^ 99;
  87. sBox[x] = sx;
  88. invSBox[sx] = x;
  89. let x2 = d[x];
  90. let x4 = d[x2];
  91. let x8 = d[x4];
  92. let t = d[sx] * 257 ^ sx * 16843008;
  93. subMix0[x] = t << 24 | t >>> 8;
  94. subMix1[x] = t << 16 | t >>> 16;
  95. subMix2[x] = t << 8 | t >>> 24;
  96. subMix3[x] = t;
  97. t = x8 * 16843009 ^ x4 * 65537 ^ x2 * 257 ^ x * 16843008;
  98. invSubMix0[sx] = t << 24 | t >>> 8;
  99. invSubMix1[sx] = t << 16 | t >>> 16;
  100. invSubMix2[sx] = t << 8 | t >>> 24;
  101. invSubMix3[sx] = t;
  102. if (!x) {
  103. x = xi = 1;
  104. } else {
  105. x = x2 ^ d[d[d[x8 ^ x2]]];
  106. xi ^= d[d[xi]];
  107. }
  108. }
  109. },
  110. expandKey(keyBuffer) {
  111. let key = this.uint8ArrayToUint32Array_(keyBuffer);
  112. let sameKey = true;
  113. let offset = 0;
  114. while (offset < key.length && sameKey) {
  115. sameKey = key[offset] === this.key[offset];
  116. offset++;
  117. }
  118. if (sameKey) {
  119. return;
  120. }
  121. this.key = key;
  122. let keySize = this.keySize = key.length;
  123. if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
  124. throw new Error("Invalid aes key size=" + keySize);
  125. }
  126. let ksRows = this.ksRows = (keySize + 6 + 1) * 4;
  127. let ksRow;
  128. let invKsRow;
  129. let keySchedule = this.keySchedule = new Uint32Array(ksRows);
  130. let invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows);
  131. let sbox = this.sBox;
  132. let rcon = this.rcon;
  133. let invSubMix = this.invSubMix;
  134. let invSubMix0 = invSubMix[0];
  135. let invSubMix1 = invSubMix[1];
  136. let invSubMix2 = invSubMix[2];
  137. let invSubMix3 = invSubMix[3];
  138. let prev;
  139. let t;
  140. for (ksRow = 0; ksRow < ksRows; ksRow++) {
  141. if (ksRow < keySize) {
  142. prev = keySchedule[ksRow] = key[ksRow];
  143. continue;
  144. }
  145. t = prev;
  146. if (ksRow % keySize === 0) {
  147. t = t << 8 | t >>> 24;
  148. t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 255] << 16 | sbox[t >>> 8 & 255] << 8 | sbox[t & 255];
  149. t ^= rcon[ksRow / keySize | 0] << 24;
  150. } else if (keySize > 6 && ksRow % keySize === 4) {
  151. t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 255] << 16 | sbox[t >>> 8 & 255] << 8 | sbox[t & 255];
  152. }
  153. keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
  154. }
  155. for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
  156. ksRow = ksRows - invKsRow;
  157. if (invKsRow & 3) {
  158. t = keySchedule[ksRow];
  159. } else {
  160. t = keySchedule[ksRow - 4];
  161. }
  162. if (invKsRow < 4 || ksRow <= 4) {
  163. invKeySchedule[invKsRow] = t;
  164. } else {
  165. invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 255]] ^ invSubMix2[sbox[t >>> 8 & 255]] ^ invSubMix3[sbox[t & 255]];
  166. }
  167. invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
  168. }
  169. },
  170. // Adding this as a method greatly improves performance.
  171. networkToHostOrderSwap(word) {
  172. return word << 24 | (word & 65280) << 8 | (word & 16711680) >> 8 | word >>> 24;
  173. },
  174. decrypt(inputArrayBuffer, offset, aesIV, removePKCS7Padding) {
  175. let nRounds = this.keySize + 6;
  176. let invKeySchedule = this.invKeySchedule;
  177. let invSBOX = this.invSBox;
  178. let invSubMix = this.invSubMix;
  179. let invSubMix0 = invSubMix[0];
  180. let invSubMix1 = invSubMix[1];
  181. let invSubMix2 = invSubMix[2];
  182. let invSubMix3 = invSubMix[3];
  183. let initVector = this.uint8ArrayToUint32Array_(aesIV);
  184. let initVector0 = initVector[0];
  185. let initVector1 = initVector[1];
  186. let initVector2 = initVector[2];
  187. let initVector3 = initVector[3];
  188. let inputInt32 = new Int32Array(inputArrayBuffer);
  189. let outputInt32 = new Int32Array(inputInt32.length);
  190. let t0, t1, t2, t3;
  191. let s0, s1, s2, s3;
  192. let inputWords0, inputWords1, inputWords2, inputWords3;
  193. let ksRow, i;
  194. let swapWord = this.networkToHostOrderSwap;
  195. while (offset < inputInt32.length) {
  196. inputWords0 = swapWord(inputInt32[offset]);
  197. inputWords1 = swapWord(inputInt32[offset + 1]);
  198. inputWords2 = swapWord(inputInt32[offset + 2]);
  199. inputWords3 = swapWord(inputInt32[offset + 3]);
  200. s0 = inputWords0 ^ invKeySchedule[0];
  201. s1 = inputWords3 ^ invKeySchedule[1];
  202. s2 = inputWords2 ^ invKeySchedule[2];
  203. s3 = inputWords1 ^ invKeySchedule[3];
  204. ksRow = 4;
  205. for (i = 1; i < nRounds; i++) {
  206. t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 255] ^ invSubMix2[s2 >> 8 & 255] ^ invSubMix3[s3 & 255] ^ invKeySchedule[ksRow];
  207. t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 255] ^ invSubMix2[s3 >> 8 & 255] ^ invSubMix3[s0 & 255] ^ invKeySchedule[ksRow + 1];
  208. t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 255] ^ invSubMix2[s0 >> 8 & 255] ^ invSubMix3[s1 & 255] ^ invKeySchedule[ksRow + 2];
  209. t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 255] ^ invSubMix2[s1 >> 8 & 255] ^ invSubMix3[s2 & 255] ^ invKeySchedule[ksRow + 3];
  210. s0 = t0;
  211. s1 = t1;
  212. s2 = t2;
  213. s3 = t3;
  214. ksRow = ksRow + 4;
  215. }
  216. t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 255] << 16 ^ invSBOX[s2 >> 8 & 255] << 8 ^ invSBOX[s3 & 255] ^ invKeySchedule[ksRow];
  217. t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 255] << 16 ^ invSBOX[s3 >> 8 & 255] << 8 ^ invSBOX[s0 & 255] ^ invKeySchedule[ksRow + 1];
  218. t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 255] << 16 ^ invSBOX[s0 >> 8 & 255] << 8 ^ invSBOX[s1 & 255] ^ invKeySchedule[ksRow + 2];
  219. t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 255] << 16 ^ invSBOX[s1 >> 8 & 255] << 8 ^ invSBOX[s2 & 255] ^ invKeySchedule[ksRow + 3];
  220. ksRow = ksRow + 3;
  221. outputInt32[offset] = swapWord(t0 ^ initVector0);
  222. outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
  223. outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
  224. outputInt32[offset + 3] = swapWord(t1 ^ initVector3);
  225. initVector0 = inputWords0;
  226. initVector1 = inputWords1;
  227. initVector2 = inputWords2;
  228. initVector3 = inputWords3;
  229. offset = offset + 4;
  230. }
  231. return removePKCS7Padding ? removePadding(outputInt32.buffer) : outputInt32.buffer;
  232. },
  233. destroy() {
  234. this.key = void 0;
  235. this.keySize = void 0;
  236. this.ksRows = void 0;
  237. this.sBox = void 0;
  238. this.invSBox = void 0;
  239. this.subMix = void 0;
  240. this.invSubMix = void 0;
  241. this.keySchedule = void 0;
  242. this.invKeySchedule = void 0;
  243. this.rcon = void 0;
  244. }
  245. };
  246. }
  247. const _sfc_main$2 = {
  248. name: "downloader",
  249. props: {
  250. url: "",
  251. title: ""
  252. },
  253. data() {
  254. return {
  255. conf: {
  256. autoDownload: true,
  257. autoSave: true,
  258. autoClose: false
  259. },
  260. show: false,
  261. tips: "m3u8 视频在线提取工具",
  262. // 顶部提示
  263. isPause: false,
  264. // 是否暂停下载
  265. isGetMP4: false,
  266. // 是否转码为 MP4 下载
  267. durationSecond: 0,
  268. // 视频持续时长
  269. isShowRefer: false,
  270. // 是否显示推送
  271. downloading: false,
  272. // 是否下载中
  273. beginTime: "",
  274. // 开始下载的时间
  275. errorNum: 0,
  276. // 错误数
  277. finishNum: 0,
  278. // 已下载数
  279. downloadIndex: 0,
  280. // 当前下载片段
  281. finishList: [],
  282. // 下载完成项目
  283. tsUrlList: [],
  284. // ts URL数组
  285. mediaFileList: [],
  286. // 下载的媒体数组
  287. isSupperStreamWrite: _monkeyWindow.streamSaver && !_monkeyWindow.streamSaver.useBlobFallback,
  288. // 当前浏览器是否支持流式下载
  289. streamWriter: null,
  290. // 文件流写入器
  291. streamDownloadIndex: 0,
  292. // 文件流写入器,正准备写入第几个视频片段
  293. rangeDownload: {
  294. // 特定范围下载
  295. isShowRange: false,
  296. // 是否显示范围下载
  297. startSegment: "",
  298. // 起始片段
  299. endSegment: "",
  300. // 截止片段
  301. targetSegment: 1
  302. // 待下载片段
  303. },
  304. aesConf: {
  305. // AES 视频解密配置
  306. method: "",
  307. // 加密算法
  308. uri: "",
  309. // key 所在文件路径
  310. iv: "",
  311. // 偏移值
  312. key: "",
  313. // 秘钥
  314. decryptor: null,
  315. // 解码器对象
  316. stringToBuffer: function(str) {
  317. return new TextEncoder().encode(str);
  318. }
  319. }
  320. };
  321. },
  322. computed: {
  323. progress() {
  324. return (this.finishNum / this.rangeDownload.targetSegment * 100).toFixed(2);
  325. }
  326. },
  327. watch: {
  328. conf: {
  329. handler(n, o) {
  330. localStorage.setItem("porn-plus", JSON.stringify(n));
  331. },
  332. deep: true
  333. },
  334. progress(n, o) {
  335. document.title = Number(n).toFixed(0) + "% " + this.title;
  336. }
  337. },
  338. created() {
  339. console.log("m3u8-downloader.vue");
  340. this.getConf();
  341. if (this.conf.autoDownload) {
  342. this.getMP4();
  343. }
  344. setInterval(this.retryAll.bind(this), 2e3);
  345. },
  346. methods: {
  347. // ajax 请求
  348. ajax(options) {
  349. options = options || {};
  350. let xhr = new XMLHttpRequest();
  351. if (options.type === "file") {
  352. xhr.responseType = "arraybuffer";
  353. }
  354. xhr.onreadystatechange = function() {
  355. if (xhr.readyState === 4) {
  356. let status = xhr.status;
  357. if (status >= 200 && status < 300) {
  358. options.success && options.success(xhr.response);
  359. } else {
  360. options.fail && options.fail(status);
  361. }
  362. }
  363. };
  364. xhr.open("GET", options.url, true);
  365. xhr.send(null);
  366. },
  367. // 合成URL
  368. applyURL(targetURL, baseURL) {
  369. baseURL = baseURL || location.href;
  370. if (targetURL.indexOf("http") === 0) {
  371. if (location.href.indexOf("https") === 0) {
  372. return targetURL.replace("http://", "https://");
  373. }
  374. return targetURL;
  375. } else if (targetURL[0] === "/") {
  376. let domain = baseURL.split("/");
  377. return domain[0] + "//" + domain[2] + targetURL;
  378. } else {
  379. let domain = baseURL.split("/");
  380. domain.pop();
  381. return domain.join("/") + "/" + targetURL;
  382. }
  383. },
  384. // 使用流式下载,边下载边保存,解决大视频文件内存不足的难题
  385. streamDownload(isMp4) {
  386. this.isGetMP4 = isMp4;
  387. let fileName = this.title || this.formatTime(/* @__PURE__ */ new Date(), "YYYY_MM_DD hh_mm_ss");
  388. this.streamWriter = _monkeyWindow.streamSaver.createWriteStream(`${fileName}.${isMp4 ? "mp4" : "ts"}`).getWriter();
  389. this.getM3U8();
  390. },
  391. // 解析为 mp4 下载
  392. getMP4() {
  393. this.isGetMP4 = true;
  394. this.getM3U8();
  395. },
  396. // 获取在线文件
  397. getM3U8(onlyGetRange) {
  398. if (!this.url) {
  399. alert("请输入链接");
  400. return;
  401. }
  402. if (this.url.toLowerCase().indexOf("m3u8") === -1)
  403. ;
  404. if (this.downloading) {
  405. alert("资源下载中,请稍后");
  406. return;
  407. }
  408. this.tips = "m3u8 文件下载中,请稍后";
  409. this.beginTime = /* @__PURE__ */ new Date();
  410. this.ajax({
  411. url: this.url,
  412. success: (m3u8Str) => {
  413. this.tsUrlList = [];
  414. this.finishList = [];
  415. m3u8Str.split("\n").forEach((item) => {
  416. if (/^[^#]/.test(item)) {
  417. this.tsUrlList.push(this.applyURL(item, this.url));
  418. this.finishList.push({
  419. title: item,
  420. status: ""
  421. });
  422. }
  423. });
  424. if (onlyGetRange) {
  425. this.rangeDownload.isShowRange = true;
  426. this.rangeDownload.endSegment = this.tsUrlList.length;
  427. this.rangeDownload.targetSegment = this.tsUrlList.length;
  428. return;
  429. } else {
  430. let startSegment = Math.max(this.rangeDownload.startSegment || 1, 1);
  431. let endSegment = Math.max(this.rangeDownload.endSegment || this.tsUrlList.length, 1);
  432. startSegment = Math.min(startSegment, this.tsUrlList.length);
  433. endSegment = Math.min(endSegment, this.tsUrlList.length);
  434. this.rangeDownload.startSegment = Math.min(startSegment, endSegment);
  435. this.rangeDownload.endSegment = Math.max(startSegment, endSegment);
  436. this.rangeDownload.targetSegment = this.rangeDownload.endSegment - this.rangeDownload.startSegment + 1;
  437. this.downloadIndex = this.rangeDownload.startSegment - 1;
  438. this.downloading = true;
  439. }
  440. if (this.isGetMP4) {
  441. let infoIndex = 0;
  442. m3u8Str.split("\n").forEach((item) => {
  443. if (item.toUpperCase().indexOf("#EXTINF:") > -1) {
  444. infoIndex++;
  445. if (this.rangeDownload.startSegment <= infoIndex && infoIndex <= this.rangeDownload.endSegment) {
  446. this.durationSecond += parseFloat(item.split("#EXTINF:")[1]);
  447. }
  448. }
  449. });
  450. }
  451. console.log("this.durationSecond", this.durationSecond);
  452. if (m3u8Str.indexOf("#EXT-X-KEY") > -1) {
  453. this.aesConf.method = (m3u8Str.match(/(.*METHOD=([^,\s]+))/) || ["", "", ""])[2];
  454. this.aesConf.uri = (m3u8Str.match(/(.*URI="([^"]+))"/) || ["", "", ""])[2];
  455. this.aesConf.iv = (m3u8Str.match(/(.*IV=([^,\s]+))/) || ["", "", ""])[2];
  456. this.aesConf.iv = this.aesConf.iv ? this.aesConf.stringToBuffer(this.aesConf.iv) : "";
  457. this.aesConf.uri = this.applyURL(this.aesConf.uri, this.url);
  458. this.getAES();
  459. } else if (this.tsUrlList.length > 0) {
  460. this.downloadTS();
  461. } else {
  462. this.alertError("资源为空,请查看链接是否有效");
  463. }
  464. },
  465. fail: () => {
  466. this.alertError("链接不正确,请查看链接是否有效");
  467. }
  468. });
  469. },
  470. // 获取AES配置
  471. getAES() {
  472. this.ajax({
  473. type: "file",
  474. url: this.aesConf.uri,
  475. success: (key) => {
  476. this.aesConf.key = key;
  477. this.aesConf.decryptor = new AESDecryptor();
  478. this.aesConf.decryptor.constructor();
  479. this.aesConf.decryptor.expandKey(this.aesConf.key);
  480. this.downloadTS();
  481. },
  482. fail: () => {
  483. this.alertError("视频已加密,可试用右下角入口的「无差别提取工具」");
  484. }
  485. });
  486. },
  487. // ts 片段的 AES 解码
  488. aesDecrypt(data, index) {
  489. let iv = this.aesConf.iv || new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, index]);
  490. return this.aesConf.decryptor.decrypt(data, 0, iv.buffer || iv, true);
  491. },
  492. // 下载分片
  493. downloadTS() {
  494. this.tips = "ts 视频碎片下载中,请稍后";
  495. let download = () => {
  496. let isPause = this.isPause;
  497. let index = this.downloadIndex;
  498. if (index >= this.rangeDownload.endSegment) {
  499. return;
  500. }
  501. this.downloadIndex++;
  502. if (this.finishList[index] && this.finishList[index].status === "") {
  503. this.finishList[index].status = "downloading";
  504. this.ajax({
  505. url: this.tsUrlList[index],
  506. type: "file",
  507. success: (file) => {
  508. this.dealTS(file, index, () => this.downloadIndex < this.rangeDownload.endSegment && !isPause && download());
  509. },
  510. fail: () => {
  511. this.errorNum++;
  512. this.finishList[index].status = "error";
  513. if (this.downloadIndex < this.rangeDownload.endSegment) {
  514. !isPause && download();
  515. }
  516. }
  517. });
  518. } else if (this.downloadIndex < this.rangeDownload.endSegment) {
  519. !isPause && download();
  520. }
  521. };
  522. for (let i = 0; i < Math.min(6, this.rangeDownload.targetSegment - this.finishNum); i++) {
  523. download();
  524. }
  525. },
  526. // 处理 ts 片段,AES 解密、mp4 转码
  527. dealTS(file, index, callback) {
  528. const data = this.aesConf.uri ? this.aesDecrypt(file, index) : file;
  529. this.conversionMp4(data, index, (afterData) => {
  530. this.mediaFileList[index - this.rangeDownload.startSegment + 1] = afterData;
  531. this.finishList[index].status = "finish";
  532. this.finishNum++;
  533. if (this.streamWriter) {
  534. for (let index2 = this.streamDownloadIndex; index2 < this.mediaFileList.length; index2++) {
  535. if (this.mediaFileList[index2]) {
  536. this.streamWriter.write(new Uint8Array(this.mediaFileList[index2]));
  537. this.mediaFileList[index2] = null;
  538. this.streamDownloadIndex = index2 + 1;
  539. } else {
  540. break;
  541. }
  542. }
  543. if (this.streamDownloadIndex >= this.rangeDownload.targetSegment) {
  544. this.streamWriter.close();
  545. }
  546. } else if (this.finishNum === this.rangeDownload.targetSegment) {
  547. let fileName = this.title || this.formatTime(this.beginTime, "YYYY_MM_DD hh_mm_ss");
  548. this.downloadFile(this.mediaFileList, fileName);
  549. }
  550. callback && callback();
  551. });
  552. },
  553. // 转码为 mp4
  554. conversionMp4(data, index, callback) {
  555. if (this.isGetMP4) {
  556. let transmuxer = new _monkeyWindow.muxjs.Transmuxer({
  557. keepOriginalTimestamps: true,
  558. duration: parseInt(this.durationSecond)
  559. });
  560. transmuxer.on("data", (segment) => {
  561. if (index === this.rangeDownload.startSegment - 1) {
  562. let data2 = new Uint8Array(segment.initSegment.byteLength + segment.data.byteLength);
  563. data2.set(segment.initSegment, 0);
  564. data2.set(segment.data, segment.initSegment.byteLength);
  565. callback(data2.buffer);
  566. } else {
  567. callback(segment.data);
  568. }
  569. });
  570. transmuxer.push(new Uint8Array(data));
  571. transmuxer.flush();
  572. } else {
  573. callback(data);
  574. }
  575. },
  576. // 暂停与恢复
  577. togglePause() {
  578. this.isPause = !this.isPause;
  579. !this.isPause && this.retryAll(true);
  580. },
  581. // 重新下载某个片段
  582. retry(index) {
  583. if (this.finishList[index].status === "error") {
  584. this.finishList[index].status = "";
  585. this.ajax({
  586. url: this.tsUrlList[index],
  587. type: "file",
  588. success: (file) => {
  589. this.errorNum--;
  590. this.dealTS(file, index);
  591. },
  592. fail: () => {
  593. this.finishList[index].status = "error";
  594. }
  595. });
  596. }
  597. },
  598. // 重新下载所有错误片段
  599. retryAll(forceRestart) {
  600. if (!this.finishList.length || this.isPause) {
  601. return;
  602. }
  603. let firstErrorIndex = this.downloadIndex;
  604. this.finishList.forEach((item, index) => {
  605. if (item.status === "error") {
  606. item.status = "";
  607. firstErrorIndex = Math.min(firstErrorIndex, index);
  608. }
  609. });
  610. this.errorNum = 0;
  611. if (this.downloadIndex >= this.rangeDownload.endSegment || forceRestart) {
  612. this.downloadIndex = firstErrorIndex;
  613. this.downloadTS();
  614. } else {
  615. this.downloadIndex = firstErrorIndex;
  616. }
  617. },
  618. // 下载整合后的TS文件
  619. downloadFile(fileDataList, fileName, forceSave = false) {
  620. this.getConf();
  621. setTimeout(() => {
  622. document.title = "下载完成 " + this.title;
  623. }, 1e3);
  624. if (!this.conf.autoSave && !forceSave) {
  625. return;
  626. }
  627. this.tips = "ts 碎片整合中,请留意浏览器下载";
  628. let fileBlob = null;
  629. let a = document.createElement("a");
  630. if (this.isGetMP4) {
  631. fileBlob = new Blob(fileDataList, { type: "video/mp4" });
  632. a.download = fileName + ".mp4";
  633. } else {
  634. fileBlob = new Blob(fileDataList, { type: "video/MP2T" });
  635. a.download = fileName + ".ts";
  636. }
  637. a.href = URL.createObjectURL(fileBlob);
  638. a.style.display = "none";
  639. document.body.appendChild(a);
  640. a.click();
  641. a.remove();
  642. setTimeout(() => {
  643. if (this.conf.autoClose) {
  644. window.opener = null;
  645. window.open("", "_self", "");
  646. window.close();
  647. }
  648. }, 1e3);
  649. },
  650. // 格式化时间
  651. formatTime(date, formatStr) {
  652. const formatType = {
  653. Y: date.getFullYear(),
  654. M: date.getMonth() + 1,
  655. D: date.getDate(),
  656. h: date.getHours(),
  657. m: date.getMinutes(),
  658. s: date.getSeconds()
  659. };
  660. return formatStr.replace(
  661. /Y+|M+|D+|h+|m+|s+/g,
  662. (target) => (new Array(target.length).join("0") + formatType[target[0]]).substr(-target.length)
  663. );
  664. },
  665. // 强制下载现有片段
  666. forceDownload() {
  667. if (this.mediaFileList.length) {
  668. let fileName = this.title || this.formatTime(this.beginTime, "YYYY_MM_DD hh_mm_ss");
  669. this.downloadFile(this.mediaFileList, fileName, true);
  670. } else {
  671. alert("当前无已下载片段");
  672. }
  673. },
  674. // 发生错误,进行提示
  675. alertError(tips) {
  676. alert(tips);
  677. this.downloading = false;
  678. this.tips = "m3u8 视频在线提取工具";
  679. },
  680. getConf() {
  681. let confStr = localStorage.getItem("porn-plus");
  682. if (confStr) {
  683. try {
  684. this.conf = JSON.parse(confStr);
  685. } catch (e) {
  686. }
  687. }
  688. }
  689. }
  690. };
  691. const _withScopeId$2 = (n) => (vue.pushScopeId("data-v-56d4409a"), n = n(), vue.popScopeId(), n);
  692. const _hoisted_1$2 = { id: "app" };
  693. const _hoisted_2$2 = { class: "info" };
  694. const _hoisted_3$2 = { class: "options" };
  695. const _hoisted_4$2 = { class: "option" };
  696. const _hoisted_5$2 = /* @__PURE__ */ _withScopeId$2(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a1" }, "自动下载", -1));
  697. const _hoisted_6$2 = { class: "option" };
  698. const _hoisted_7$2 = /* @__PURE__ */ _withScopeId$2(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a2" }, "下载完成后自动保存", -1));
  699. const _hoisted_8$2 = { class: "option" };
  700. const _hoisted_9$2 = /* @__PURE__ */ _withScopeId$2(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a3" }, "保存后自动关闭", -1));
  701. const _hoisted_10$2 = {
  702. key: 0,
  703. class: "row2"
  704. };
  705. const _hoisted_11$2 = /* @__PURE__ */ _withScopeId$2(() => /* @__PURE__ */ vue.createElementVNode("span", null, "保存", -1));
  706. const _hoisted_12$2 = [
  707. _hoisted_11$2
  708. ];
  709. const _hoisted_13$2 = { class: "wrapper" };
  710. const _hoisted_14$2 = { class: "m-p-input-container" };
  711. const _hoisted_15$2 = ["value"];
  712. const _hoisted_16$2 = {
  713. key: 1,
  714. class: "disable big-btn"
  715. };
  716. const _hoisted_17$2 = /* @__PURE__ */ _withScopeId$2(() => /* @__PURE__ */ vue.createElementVNode("span", null, "保存", -1));
  717. const _hoisted_18$2 = [
  718. _hoisted_17$2
  719. ];
  720. const _hoisted_19$1 = { class: "error-btns" };
  721. const _hoisted_20 = { class: "m-p-tips" };
  722. const _hoisted_21 = { class: "m-p-segment" };
  723. const _hoisted_22 = ["title", "onClick"];
  724. function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
  725. return vue.openBlock(), vue.createElementBlock("section", _hoisted_1$2, [
  726. vue.createElementVNode("div", _hoisted_2$2, [
  727. vue.createElementVNode("div", {
  728. class: "progress",
  729. onClick: _cache[0] || (_cache[0] = ($event) => $data.show = !$data.show)
  730. }, [
  731. vue.createElementVNode("div", {
  732. class: "bar",
  733. style: vue.normalizeStyle({ width: $options.progress + "%" })
  734. }, null, 4)
  735. ]),
  736. vue.createElementVNode("div", _hoisted_3$2, [
  737. vue.createElementVNode("div", _hoisted_4$2, [
  738. _hoisted_5$2,
  739. vue.withDirectives(vue.createElementVNode("input", {
  740. id: "a1",
  741. type: "checkbox",
  742. "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => $data.conf.autoDownload = $event)
  743. }, null, 512), [
  744. [vue.vModelCheckbox, $data.conf.autoDownload]
  745. ])
  746. ]),
  747. vue.createElementVNode("div", _hoisted_6$2, [
  748. _hoisted_7$2,
  749. vue.withDirectives(vue.createElementVNode("input", {
  750. id: "a2",
  751. type: "checkbox",
  752. "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => $data.conf.autoSave = $event)
  753. }, null, 512), [
  754. [vue.vModelCheckbox, $data.conf.autoSave]
  755. ])
  756. ]),
  757. vue.createElementVNode("div", _hoisted_8$2, [
  758. _hoisted_9$2,
  759. vue.withDirectives(vue.createElementVNode("input", {
  760. id: "a3",
  761. type: "checkbox",
  762. "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => $data.conf.autoClose = $event)
  763. }, null, 512), [
  764. [vue.vModelCheckbox, $data.conf.autoClose]
  765. ])
  766. ])
  767. ])
  768. ]),
  769. !$data.show ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_10$2, [
  770. $data.finishNum === $data.rangeDownload.targetSegment && $data.rangeDownload.targetSegment > 0 ? (vue.openBlock(), vue.createElementBlock("div", {
  771. key: 0,
  772. class: "big-btn",
  773. onClick: _cache[4] || (_cache[4] = (...args) => $options.forceDownload && $options.forceDownload(...args))
  774. }, _hoisted_12$2)) : vue.createCommentVNode("", true),
  775. vue.createElementVNode("div", {
  776. class: "big-btn",
  777. onClick: _cache[5] || (_cache[5] = ($event) => $data.show = !$data.show)
  778. }, [
  779. vue.createElementVNode("span", null, vue.toDisplayString(!$data.show ? "展开" : "收起") + "下载详情", 1)
  780. ])
  781. ])) : vue.createCommentVNode("", true),
  782. vue.withDirectives(vue.createElementVNode("div", _hoisted_13$2, [
  783. vue.createElementVNode("section", _hoisted_14$2, [
  784. vue.createElementVNode("input", {
  785. type: "text",
  786. value: $props.url,
  787. disabled: true,
  788. placeholder: "请输入 m3u8 链接"
  789. }, null, 8, _hoisted_15$2),
  790. !$data.downloading ? (vue.openBlock(), vue.createElementBlock("div", {
  791. key: 0,
  792. class: "big-btn",
  793. onClick: _cache[6] || (_cache[6] = (...args) => $options.getMP4 && $options.getMP4(...args))
  794. }, "下载")) : $data.finishNum === $data.rangeDownload.targetSegment && $data.rangeDownload.targetSegment > 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_16$2, " 下载完成 ")) : (vue.openBlock(), vue.createElementBlock("div", {
  795. key: 2,
  796. class: "big-btn",
  797. onClick: _cache[7] || (_cache[7] = (...args) => $options.togglePause && $options.togglePause(...args))
  798. }, vue.toDisplayString($data.isPause ? "恢复下载" : "暂停下载"), 1)),
  799. $data.finishNum === $data.rangeDownload.targetSegment && $data.rangeDownload.targetSegment > 0 && !$data.conf.autoSave ? (vue.openBlock(), vue.createElementBlock("div", {
  800. key: 3,
  801. class: "big-btn",
  802. onClick: _cache[8] || (_cache[8] = (...args) => $options.forceDownload && $options.forceDownload(...args))
  803. }, _hoisted_18$2)) : vue.createCommentVNode("", true),
  804. vue.createElementVNode("div", {
  805. class: "big-btn detail",
  806. onClick: _cache[9] || (_cache[9] = ($event) => $data.show = !$data.show)
  807. }, [
  808. vue.createElementVNode("span", null, vue.toDisplayString(!$data.show ? "展开" : "收起") + "下载详情", 1)
  809. ])
  810. ]),
  811. $data.finishList.length > 0 ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 0 }, [
  812. vue.createElementVNode("div", _hoisted_19$1, [
  813. $data.errorNum && $data.downloadIndex >= $data.rangeDownload.targetSegment ? (vue.openBlock(), vue.createElementBlock("div", {
  814. key: 0,
  815. class: "m-p-retry",
  816. onClick: _cache[10] || (_cache[10] = (...args) => $options.retryAll && $options.retryAll(...args))
  817. }, " 重新下载错误片段 ")) : vue.createCommentVNode("", true),
  818. $data.mediaFileList.length && !$data.streamWriter ? (vue.openBlock(), vue.createElementBlock("div", {
  819. key: 1,
  820. class: "m-p-force",
  821. onClick: _cache[11] || (_cache[11] = (...args) => $options.forceDownload && $options.forceDownload(...args))
  822. }, "强制下载现有片段 ")) : vue.createCommentVNode("", true)
  823. ]),
  824. vue.createElementVNode("div", _hoisted_20, "待下载碎片总量:" + vue.toDisplayString($data.rangeDownload.targetSegment) + ",已下载:" + vue.toDisplayString($data.finishNum) + ",错误:" + vue.toDisplayString($data.errorNum) + ",进度:" + vue.toDisplayString($options.progress) + "% ", 1),
  825. vue.createElementVNode("div", {
  826. class: vue.normalizeClass(["m-p-tips", [$data.errorNum ? "error-tips" : ""]])
  827. }, " 若某视频碎片下载发生错误,将标记为红色,可点击相应图标进行重试 ", 2),
  828. vue.createElementVNode("section", _hoisted_21, [
  829. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($data.finishList, (item, index) => {
  830. return vue.openBlock(), vue.createElementBlock("div", {
  831. class: vue.normalizeClass(["item", [item.status]]),
  832. title: item.title,
  833. onClick: ($event) => $options.retry(index)
  834. }, vue.toDisplayString(index + 1), 11, _hoisted_22);
  835. }), 256))
  836. ])
  837. ], 64)) : vue.createCommentVNode("", true)
  838. ], 512), [
  839. [vue.vShow, $data.show]
  840. ])
  841. ]);
  842. }
  843. const Downloader = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$1], ["__scopeId", "data-v-56d4409a"]]);
  844. let videoBlob;
  845. const _sfc_main$1 = {
  846. name: "mp4-downloader",
  847. props: {
  848. url: "",
  849. name: ""
  850. },
  851. data() {
  852. return {
  853. conf: {
  854. autoDownload: true,
  855. autoSave: true,
  856. autoClose: false
  857. },
  858. progress: 0,
  859. show: false,
  860. tips: "m3u8 视频在线提取工具",
  861. // 顶部提示
  862. isPause: false,
  863. // 是否暂停下载
  864. isGetMP4: false
  865. // 是否转码为 MP4 下载
  866. };
  867. },
  868. watch: {
  869. conf: {
  870. handler(n, o) {
  871. localStorage.setItem("porn-plus", JSON.stringify(n));
  872. },
  873. deep: true
  874. },
  875. progress(n, o) {
  876. document.title = Number(n).toFixed(0) + "% " + this.name;
  877. }
  878. },
  879. created() {
  880. console.log("mp4-downloader.vue");
  881. this.getConf();
  882. if (this.conf.autoDownload) {
  883. this.download();
  884. }
  885. },
  886. methods: {
  887. getConf() {
  888. let confStr = localStorage.getItem("porn-plus");
  889. if (confStr) {
  890. try {
  891. this.conf = JSON.parse(confStr);
  892. } catch (e) {
  893. }
  894. }
  895. },
  896. download() {
  897. let xhr = new XMLHttpRequest();
  898. xhr.open("GET", this.url, true);
  899. xhr.responseType = "blob";
  900. xhr.onprogress = (event) => {
  901. this.progress = Number(Number(event.loaded / event.total).toFixed(2)) * 100;
  902. console.log("进度", this.progress);
  903. };
  904. xhr.onload = () => {
  905. if (xhr.readyState === 4 && xhr.status === 200) {
  906. videoBlob = xhr.response;
  907. this.progress = 100;
  908. this.getConf();
  909. if (this.conf.autoSave) {
  910. this.save();
  911. }
  912. }
  913. };
  914. xhr.send();
  915. },
  916. save() {
  917. if (!videoBlob)
  918. return;
  919. this.getConf();
  920. let a = document.createElement("a");
  921. a.style.display = "none";
  922. document.body.appendChild(a);
  923. let url = window.URL.createObjectURL(videoBlob);
  924. a.href = url;
  925. a.download = this.name + ".mp4";
  926. a.click();
  927. window.URL.revokeObjectURL(url);
  928. a.remove();
  929. setTimeout(() => {
  930. if (this.conf.autoClose) {
  931. window.opener = null;
  932. window.open("", "_self", "");
  933. window.close();
  934. }
  935. }, 1e3);
  936. }
  937. }
  938. };
  939. const _withScopeId$1 = (n) => (vue.pushScopeId("data-v-429419b2"), n = n(), vue.popScopeId(), n);
  940. const _hoisted_1$1 = { class: "mp4-downloader" };
  941. const _hoisted_2$1 = { class: "info" };
  942. const _hoisted_3$1 = { class: "options" };
  943. const _hoisted_4$1 = { class: "option" };
  944. const _hoisted_5$1 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a1" }, "自动下载", -1));
  945. const _hoisted_6$1 = { class: "option" };
  946. const _hoisted_7$1 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a2" }, "下载完成后自动保存", -1));
  947. const _hoisted_8$1 = { class: "option" };
  948. const _hoisted_9$1 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("label", { for: "a3" }, "保存后自动关闭", -1));
  949. const _hoisted_10$1 = {
  950. key: 0,
  951. class: "row2"
  952. };
  953. const _hoisted_11$1 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("span", null, "保存", -1));
  954. const _hoisted_12$1 = [
  955. _hoisted_11$1
  956. ];
  957. const _hoisted_13$1 = { class: "wrapper" };
  958. const _hoisted_14$1 = { class: "m-p-input-container" };
  959. const _hoisted_15$1 = ["value"];
  960. const _hoisted_16$1 = {
  961. key: 1,
  962. class: "disable big-btn"
  963. };
  964. const _hoisted_17$1 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("span", null, "保存", -1));
  965. const _hoisted_18$1 = [
  966. _hoisted_17$1
  967. ];
  968. const _hoisted_19 = { class: "m-p-tips" };
  969. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  970. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [
  971. vue.createElementVNode("div", _hoisted_2$1, [
  972. vue.createElementVNode("div", {
  973. class: "progress",
  974. onClick: _cache[0] || (_cache[0] = ($event) => $data.show = !$data.show)
  975. }, [
  976. vue.createElementVNode("div", {
  977. class: "bar",
  978. style: vue.normalizeStyle({ width: $data.progress + "%" })
  979. }, null, 4)
  980. ]),
  981. vue.createElementVNode("div", _hoisted_3$1, [
  982. vue.createElementVNode("div", _hoisted_4$1, [
  983. _hoisted_5$1,
  984. vue.withDirectives(vue.createElementVNode("input", {
  985. id: "a1",
  986. type: "checkbox",
  987. "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => $data.conf.autoDownload = $event)
  988. }, null, 512), [
  989. [vue.vModelCheckbox, $data.conf.autoDownload]
  990. ])
  991. ]),
  992. vue.createElementVNode("div", _hoisted_6$1, [
  993. _hoisted_7$1,
  994. vue.withDirectives(vue.createElementVNode("input", {
  995. id: "a2",
  996. type: "checkbox",
  997. "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => $data.conf.autoSave = $event)
  998. }, null, 512), [
  999. [vue.vModelCheckbox, $data.conf.autoSave]
  1000. ])
  1001. ]),
  1002. vue.createElementVNode("div", _hoisted_8$1, [
  1003. _hoisted_9$1,
  1004. vue.withDirectives(vue.createElementVNode("input", {
  1005. id: "a3",
  1006. type: "checkbox",
  1007. "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => $data.conf.autoClose = $event)
  1008. }, null, 512), [
  1009. [vue.vModelCheckbox, $data.conf.autoClose]
  1010. ])
  1011. ])
  1012. ])
  1013. ]),
  1014. !$data.show ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_10$1, [
  1015. vue.createElementVNode("div", {
  1016. class: "big-btn",
  1017. onClick: _cache[4] || (_cache[4] = (...args) => $options.save && $options.save(...args))
  1018. }, _hoisted_12$1),
  1019. vue.createElementVNode("div", {
  1020. class: "big-btn",
  1021. onClick: _cache[5] || (_cache[5] = ($event) => $data.show = !$data.show)
  1022. }, [
  1023. vue.createElementVNode("span", null, vue.toDisplayString(!$data.show ? "展开" : "收起") + "下载详情", 1)
  1024. ])
  1025. ])) : vue.createCommentVNode("", true),
  1026. vue.withDirectives(vue.createElementVNode("div", _hoisted_13$1, [
  1027. vue.createElementVNode("section", _hoisted_14$1, [
  1028. vue.createElementVNode("input", {
  1029. type: "text",
  1030. value: $props.url,
  1031. disabled: true,
  1032. placeholder: "请输入 链接"
  1033. }, null, 8, _hoisted_15$1),
  1034. $data.progress === 0 ? (vue.openBlock(), vue.createElementBlock("div", {
  1035. key: 0,
  1036. class: "big-btn",
  1037. onClick: _cache[6] || (_cache[6] = (...args) => $options.download && $options.download(...args))
  1038. }, "下载")) : $data.progress === 100 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_16$1, " 下载完成 ")) : vue.createCommentVNode("", true),
  1039. $data.progress === 100 ? (vue.openBlock(), vue.createElementBlock("div", {
  1040. key: 2,
  1041. class: "big-btn",
  1042. onClick: _cache[7] || (_cache[7] = (...args) => $options.save && $options.save(...args))
  1043. }, _hoisted_18$1)) : vue.createCommentVNode("", true),
  1044. vue.createElementVNode("div", {
  1045. class: "big-btn detail",
  1046. onClick: _cache[8] || (_cache[8] = ($event) => $data.show = !$data.show)
  1047. }, [
  1048. vue.createElementVNode("span", null, vue.toDisplayString(!$data.show ? "展开" : "收起") + "下载详情", 1)
  1049. ])
  1050. ]),
  1051. vue.createElementVNode("div", _hoisted_19, "进度:" + vue.toDisplayString($data.progress) + "%", 1)
  1052. ], 512), [
  1053. [vue.vShow, $data.show]
  1054. ])
  1055. ]);
  1056. }
  1057. const Mp4Downloader = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__scopeId", "data-v-429419b2"]]);
  1058. const _withScopeId = (n) => (vue.pushScopeId("data-v-a069d05a"), n = n(), vue.popScopeId(), n);
  1059. const _hoisted_1 = { class: "content" };
  1060. const _hoisted_2 = { class: "left" };
  1061. const _hoisted_3 = { class: "big-title" };
  1062. const _hoisted_4 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("path", {
  1063. d: "M9 18V42H39V18L24 6L9 18Z",
  1064. fill: "none",
  1065. stroke: "#ffffff",
  1066. "stroke-width": "4",
  1067. "stroke-linecap": "round",
  1068. "stroke-linejoin": "round"
  1069. }, null, -1));
  1070. const _hoisted_5 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("path", {
  1071. d: "M19 29V42H29V29H19Z",
  1072. fill: "none",
  1073. stroke: "#ffffff",
  1074. "stroke-width": "4",
  1075. "stroke-linejoin": "round"
  1076. }, null, -1));
  1077. const _hoisted_6 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("path", {
  1078. d: "M9 42H39",
  1079. stroke: "#ffffff",
  1080. "stroke-width": "4",
  1081. "stroke-linecap": "round"
  1082. }, null, -1));
  1083. const _hoisted_7 = [
  1084. _hoisted_4,
  1085. _hoisted_5,
  1086. _hoisted_6
  1087. ];
  1088. const _hoisted_8 = { class: "comments" };
  1089. const _hoisted_9 = { class: "item" };
  1090. const _hoisted_10 = { class: "title" };
  1091. const _hoisted_11 = {
  1092. key: 0,
  1093. class: "quote"
  1094. };
  1095. const _hoisted_12 = { class: "replay" };
  1096. const _hoisted_13 = { class: "video" };
  1097. const _hoisted_14 = { class: "title" };
  1098. const _hoisted_15 = {
  1099. class: "author",
  1100. target: "_blank"
  1101. };
  1102. const _hoisted_16 = ["href"];
  1103. const _hoisted_17 = { class: "right" };
  1104. const _hoisted_18 = { class: "big-title" };
  1105. const _sfc_main = {
  1106. __name: "App",
  1107. setup(__props) {
  1108. const dpRef = vue.ref(null);
  1109. const dp = vue.ref(null);
  1110. const recommendRef = vue.ref(null);
  1111. const data = vue.reactive({
  1112. comments: [],
  1113. info: window.info,
  1114. show: true
  1115. });
  1116. vue.watch(() => data.show, (n, o) => {
  1117. var _a;
  1118. if (n) {
  1119. pauseOriginVideo();
  1120. document.body.style.overflow = "hidden";
  1121. } else {
  1122. (_a = dp.value) == null ? void 0 : _a.pause();
  1123. document.body.style.overflow = "unset";
  1124. }
  1125. }, { immediate: true });
  1126. vue.onMounted(() => {
  1127. recommendRef.value.append($("h3").next().next()[0]);
  1128. $("br").remove();
  1129. initComments();
  1130. initVideo();
  1131. checkOriginVideoIsPlay();
  1132. });
  1133. function pauseOriginVideo() {
  1134. if ($(".vjs-playing").length) {
  1135. $("#player_one_html5_api").click();
  1136. }
  1137. }
  1138. function checkOriginVideoIsPlay() {
  1139. setInterval(() => {
  1140. if (data.show) {
  1141. pauseOriginVideo();
  1142. }
  1143. }, 3e3);
  1144. }
  1145. function initComments() {
  1146. $.ajax({
  1147. url: `${location.origin}/show_comments2.php?VID=${data.info.video.vid}&start=1&comment_per_page=30`,
  1148. success(r) {
  1149. let h = $(r);
  1150. h.each(function() {
  1151. if (this.tagName === "TABLE") {
  1152. let name = $(this).find("a:first").remove().text().trim();
  1153. let time = $(this).find(".comment-info").contents()[4];
  1154. let body = $(this).find(".comment-body");
  1155. let quote = body.find(".comment_quote").text().replaceAll(" ", "").replaceAll("\n", "");
  1156. body.find(".comment_quote").remove();
  1157. let replay = body.find("a").remove().end().text().trim();
  1158. if (replay.includes("请不要发广告或与视频无关的评论") || quote.includes("请不要发广告或与视频无关的评论"))
  1159. ;
  1160. else {
  1161. data.comments.push({
  1162. name,
  1163. time: $(time).text(),
  1164. quote,
  1165. replay
  1166. });
  1167. }
  1168. }
  1169. });
  1170. }
  1171. });
  1172. }
  1173. function initVideo() {
  1174. if (!dpRef.value)
  1175. return;
  1176. let config = {
  1177. url: data.info.video.url,
  1178. type: data.info.video.type
  1179. };
  1180. if (config.type === "customHls") {
  1181. config.customType = {
  1182. customHls: function(video, player) {
  1183. const hls = new _monkeyWindow.Hls();
  1184. hls.loadSource(video.src);
  1185. hls.attachMedia(video);
  1186. }
  1187. };
  1188. }
  1189. dp.value = new _monkeyWindow.DPlayer({
  1190. container: dpRef.value,
  1191. autoplay: true,
  1192. video: config
  1193. });
  1194. dp.value.seek(10);
  1195. }
  1196. function goHome() {
  1197. location.href = "https://91porn.com/index.php";
  1198. }
  1199. return (_ctx, _cache) => {
  1200. return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
  1201. vue.withDirectives(vue.createElementVNode("div", _hoisted_1, [
  1202. vue.createElementVNode("div", _hoisted_2, [
  1203. vue.createElementVNode("div", _hoisted_3, [
  1204. vue.createTextVNode(" 评论 "),
  1205. (vue.openBlock(), vue.createElementBlock("svg", {
  1206. onClick: goHome,
  1207. class: "home-icon",
  1208. width: "24",
  1209. height: "24",
  1210. viewBox: "0 0 48 48",
  1211. fill: "none",
  1212. xmlns: "http://www.w3.org/2000/svg"
  1213. }, _hoisted_7))
  1214. ]),
  1215. vue.createElementVNode("div", _hoisted_8, [
  1216. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(data.comments, (item) => {
  1217. return vue.openBlock(), vue.createElementBlock("div", _hoisted_9, [
  1218. vue.createElementVNode("div", _hoisted_10, [
  1219. vue.createElementVNode("span", null, vue.toDisplayString(item.name), 1),
  1220. vue.createElementVNode("span", null, vue.toDisplayString(item.time), 1)
  1221. ]),
  1222. item.quote ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_11, vue.toDisplayString(item.quote), 1)) : vue.createCommentVNode("", true),
  1223. vue.createElementVNode("div", _hoisted_12, vue.toDisplayString(item.replay), 1)
  1224. ]);
  1225. }), 256))
  1226. ])
  1227. ]),
  1228. vue.createElementVNode("div", _hoisted_13, [
  1229. vue.createElementVNode("div", {
  1230. id: "dplayer",
  1231. ref_key: "dpRef",
  1232. ref: dpRef,
  1233. style: { "height": "70vh" }
  1234. }, null, 512),
  1235. vue.createElementVNode("div", _hoisted_14, vue.toDisplayString(data.info.video.name), 1),
  1236. vue.createElementVNode("div", _hoisted_15, " 添加时间: " + vue.toDisplayString(data.info.video.date), 1),
  1237. data.info.author.name ? (vue.openBlock(), vue.createElementBlock("a", {
  1238. key: 0,
  1239. href: data.info.author.url,
  1240. class: "author",
  1241. target: "_blank"
  1242. }, " 作者: " + vue.toDisplayString(data.info.author.name), 9, _hoisted_16)) : vue.createCommentVNode("", true),
  1243. data.info.video.type === "auto" ? (vue.openBlock(), vue.createBlock(Mp4Downloader, {
  1244. key: 1,
  1245. name: data.info.video.name,
  1246. url: data.info.video.url
  1247. }, null, 8, ["name", "url"])) : (vue.openBlock(), vue.createBlock(Downloader, {
  1248. key: 2,
  1249. title: data.info.video.name,
  1250. url: data.info.video.url
  1251. }, null, 8, ["title", "url"]))
  1252. ]),
  1253. vue.createElementVNode("div", _hoisted_17, [
  1254. vue.createElementVNode("div", _hoisted_18, [
  1255. vue.createTextVNode(" 本月热播 "),
  1256. vue.createElementVNode("div", {
  1257. class: "close",
  1258. onClick: _cache[0] || (_cache[0] = ($event) => data.show = !data.show)
  1259. })
  1260. ]),
  1261. vue.createElementVNode("div", {
  1262. class: "list",
  1263. ref_key: "recommendRef",
  1264. ref: recommendRef
  1265. }, null, 512)
  1266. ])
  1267. ], 512), [
  1268. [vue.vShow, data.show]
  1269. ]),
  1270. vue.withDirectives(vue.createElementVNode("div", {
  1271. class: "showBtn",
  1272. onClick: _cache[1] || (_cache[1] = ($event) => data.show = !data.show)
  1273. }, "展开脚本", 512), [
  1274. [vue.vShow, !data.show]
  1275. ])
  1276. ], 64);
  1277. };
  1278. }
  1279. };
  1280. const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-a069d05a"]]);
  1281. window.info = {
  1282. author: {
  1283. name: "",
  1284. url: "",
  1285. vid: ""
  1286. },
  1287. video: {
  1288. name: "",
  1289. url: "",
  1290. vid: ""
  1291. }
  1292. // videoUrl:'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8',
  1293. };
  1294. function init() {
  1295. if (window.info.videoUrl)
  1296. return;
  1297. const videoWrapper = $("#player_one_html5_api");
  1298. const userWrapper = $(".title-yakov");
  1299. if ((userWrapper == null ? void 0 : userWrapper.length) === 2) {
  1300. let href = userWrapper[1].firstElementChild.href;
  1301. let start = href.indexOf("UID");
  1302. let uid = href.substring(start + 4);
  1303. window.info.author = {
  1304. name: userWrapper.find(".title").text(),
  1305. url: `https://91porn.com/uvideos.php?UID=${uid}&type=public`,
  1306. uid
  1307. };
  1308. }
  1309. if (!videoWrapper[0]) {
  1310. return;
  1311. } else {
  1312. window.info.video = {
  1313. name: document.title.replace("Chinese homemade video", ""),
  1314. url: videoWrapper.find("source")[0].src,
  1315. vid: $("#favorite #VID").text(),
  1316. date: $(".title-yakov")[0].innerText
  1317. };
  1318. if (window.info.video.url.toLowerCase().indexOf("m3u8") === -1) {
  1319. window.info.video.type = "auto";
  1320. } else {
  1321. window.info.video.type = "customHls";
  1322. }
  1323. vue.createApp(App).mount(
  1324. (() => {
  1325. const app = document.createElement("div");
  1326. document.body.append(app);
  1327. return app;
  1328. })()
  1329. );
  1330. }
  1331. console.log(window.info);
  1332. }
  1333. try {
  1334. let style2 = `
  1335. .ad_img{display:none;}
  1336. `;
  1337. let addStyle2 = document.createElement("style");
  1338. addStyle2.rel = "stylesheet";
  1339. addStyle2.type = "text/css";
  1340. addStyle2.innerHTML = style2;
  1341. window.document.head.append(addStyle2);
  1342. $("iframe").hide();
  1343. let l = window.location;
  1344. if (l.pathname === "/index.php") {
  1345. } else if (l.pathname === "/view_video.php") {
  1346. init();
  1347. }
  1348. } catch (e) {
  1349. console.log("init报错了", e);
  1350. }
  1351.  
  1352. })(Vue);