pornHelper

下载PornHub视频和封面

  1. // ==UserScript==
  2. // @name pornHelper
  3. // @namespace npm/vite-plugin-monkey
  4. // @version 1.0.0
  5. // @author Kin
  6. // @description 下载PornHub视频和封面
  7. // @license MIT
  8. // @icon https://pornhub.com/favicon.ico
  9. // @homepage https://github.com/Yorushika-fan/pornHelper
  10. // @match *://*.pornhub.com/*
  11. // @require https://cdn.jsdelivr.net/npm/vue@3.5.12/dist/vue.global.prod.js
  12. // @require https://unpkg.com/vue-demi@latest/lib/index.iife.js
  13. // @require data:application/javascript,window.Vue%3DVue%3B
  14. // @require https://cdn.jsdelivr.net/npm/element-plus@2.8.5/dist/index.full.min.js
  15. // @resource ElementPlus https://cdn.jsdelivr.net/npm/element-plus@2.8.5/dist/index.full.min.css
  16. // @grant GM_addStyle
  17. // @grant GM_download
  18. // @grant GM_getResourceText
  19. // @grant GM_xmlhttpRequest
  20. // @grant unsafeWindow
  21. // ==/UserScript==
  22.  
  23. (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const r=document.createElement("style");r.textContent=t,document.head.append(r)})(" *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.fixed{position:fixed}.right-4{right:1rem}.top-16{top:4rem}.z-10{z-index:10}.mb-4{margin-bottom:1rem}.flex{display:flex}.flex-grow{flex-grow:1}.flex-row{flex-direction:row}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.\\!rounded-lg{border-radius:.5rem!important}.rounded{border-radius:.25rem}.\\!bg-\\[\\#ff9900\\]{--tw-bg-opacity: 1 !important;background-color:rgb(255 153 0 / var(--tw-bg-opacity))!important}.\\!bg-gray-200{--tw-bg-opacity: 1 !important;background-color:rgb(229 231 235 / var(--tw-bg-opacity))!important}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.\\!py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.\\!text-base{font-size:1rem!important;line-height:1.5rem!important}.\\!font-bold{font-weight:700!important}.font-bold{font-weight:700}.\\!text-black{--tw-text-opacity: 1 !important;color:rgb(0 0 0 / var(--tw-text-opacity))!important}.\\!text-gray-700{--tw-text-opacity: 1 !important;color:rgb(55 65 81 / var(--tw-text-opacity))!important}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.\\!transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important;transition-duration:.15s!important}.hover\\:\\!bg-\\[\\#ff6600\\]:hover{--tw-bg-opacity: 1 !important;background-color:rgb(255 102 0 / var(--tw-bg-opacity))!important}.hover\\:\\!bg-gray-300:hover{--tw-bg-opacity: 1 !important;background-color:rgb(209 213 219 / var(--tw-bg-opacity))!important}.hover\\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity))}.hover\\:bg-green-600:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))} ");
  24.  
  25. (function (vue, elementPlus) {
  26. 'use strict';
  27.  
  28. const cssLoader = (e) => {
  29. const t = GM_getResourceText(e);
  30. return GM_addStyle(t), t;
  31. };
  32. cssLoader("ElementPlus");
  33. var _GM_download = /* @__PURE__ */ (() => typeof GM_download != "undefined" ? GM_download : void 0)();
  34. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  35. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  36. var _a;
  37. const isClient = typeof window !== "undefined";
  38. const isString = (val) => typeof val === "string";
  39. const noop = () => {
  40. };
  41. isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && /iP(ad|hone|od)/.test(window.navigator.userAgent);
  42. function resolveUnref(r) {
  43. return typeof r === "function" ? r() : vue.unref(r);
  44. }
  45. function createFilterWrapper(filter, fn) {
  46. function wrapper(...args) {
  47. return new Promise((resolve, reject) => {
  48. Promise.resolve(filter(() => fn.apply(this, args), { fn, thisArg: this, args })).then(resolve).catch(reject);
  49. });
  50. }
  51. return wrapper;
  52. }
  53. function throttleFilter(ms, trailing = true, leading = true, rejectOnCancel = false) {
  54. let lastExec = 0;
  55. let timer;
  56. let isLeading = true;
  57. let lastRejector = noop;
  58. let lastValue;
  59. const clear = () => {
  60. if (timer) {
  61. clearTimeout(timer);
  62. timer = void 0;
  63. lastRejector();
  64. lastRejector = noop;
  65. }
  66. };
  67. const filter = (_invoke) => {
  68. const duration = resolveUnref(ms);
  69. const elapsed = Date.now() - lastExec;
  70. const invoke = () => {
  71. return lastValue = _invoke();
  72. };
  73. clear();
  74. if (duration <= 0) {
  75. lastExec = Date.now();
  76. return invoke();
  77. }
  78. if (elapsed > duration && (leading || !isLeading)) {
  79. lastExec = Date.now();
  80. invoke();
  81. } else if (trailing) {
  82. lastValue = new Promise((resolve, reject) => {
  83. lastRejector = rejectOnCancel ? reject : resolve;
  84. timer = setTimeout(() => {
  85. lastExec = Date.now();
  86. isLeading = true;
  87. resolve(invoke());
  88. clear();
  89. }, Math.max(0, duration - elapsed));
  90. });
  91. }
  92. if (!leading && !timer)
  93. timer = setTimeout(() => isLeading = true, duration);
  94. isLeading = false;
  95. return lastValue;
  96. };
  97. return filter;
  98. }
  99. function identity(arg) {
  100. return arg;
  101. }
  102. function tryOnScopeDispose(fn) {
  103. if (vue.getCurrentScope()) {
  104. vue.onScopeDispose(fn);
  105. return true;
  106. }
  107. return false;
  108. }
  109. function useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) {
  110. return createFilterWrapper(throttleFilter(ms, trailing, leading, rejectOnCancel), fn);
  111. }
  112. function tryOnMounted(fn, sync = true) {
  113. if (vue.getCurrentInstance())
  114. vue.onMounted(fn);
  115. else if (sync)
  116. fn();
  117. else
  118. vue.nextTick(fn);
  119. }
  120. function useTimeoutFn(cb, interval, options = {}) {
  121. const {
  122. immediate = true
  123. } = options;
  124. const isPending = vue.ref(false);
  125. let timer = null;
  126. function clear() {
  127. if (timer) {
  128. clearTimeout(timer);
  129. timer = null;
  130. }
  131. }
  132. function stop() {
  133. isPending.value = false;
  134. clear();
  135. }
  136. function start(...args) {
  137. clear();
  138. isPending.value = true;
  139. timer = setTimeout(() => {
  140. isPending.value = false;
  141. timer = null;
  142. cb(...args);
  143. }, resolveUnref(interval));
  144. }
  145. if (immediate) {
  146. isPending.value = true;
  147. if (isClient)
  148. start();
  149. }
  150. tryOnScopeDispose(stop);
  151. return {
  152. isPending: vue.readonly(isPending),
  153. start,
  154. stop
  155. };
  156. }
  157. function unrefElement(elRef) {
  158. var _a2;
  159. const plain = resolveUnref(elRef);
  160. return (_a2 = plain == null ? void 0 : plain.$el) != null ? _a2 : plain;
  161. }
  162. const defaultWindow = isClient ? window : void 0;
  163. const defaultNavigator = isClient ? window.navigator : void 0;
  164. function useEventListener(...args) {
  165. let target;
  166. let events;
  167. let listeners;
  168. let options;
  169. if (isString(args[0]) || Array.isArray(args[0])) {
  170. [events, listeners, options] = args;
  171. target = defaultWindow;
  172. } else {
  173. [target, events, listeners, options] = args;
  174. }
  175. if (!target)
  176. return noop;
  177. if (!Array.isArray(events))
  178. events = [events];
  179. if (!Array.isArray(listeners))
  180. listeners = [listeners];
  181. const cleanups = [];
  182. const cleanup = () => {
  183. cleanups.forEach((fn) => fn());
  184. cleanups.length = 0;
  185. };
  186. const register = (el, event, listener, options2) => {
  187. el.addEventListener(event, listener, options2);
  188. return () => el.removeEventListener(event, listener, options2);
  189. };
  190. const stopWatch = vue.watch(() => [unrefElement(target), resolveUnref(options)], ([el, options2]) => {
  191. cleanup();
  192. if (!el)
  193. return;
  194. cleanups.push(...events.flatMap((event) => {
  195. return listeners.map((listener) => register(el, event, listener, options2));
  196. }));
  197. }, { immediate: true, flush: "post" });
  198. const stop = () => {
  199. stopWatch();
  200. cleanup();
  201. };
  202. tryOnScopeDispose(stop);
  203. return stop;
  204. }
  205. function useSupported(callback, sync = false) {
  206. const isSupported = vue.ref();
  207. const update = () => isSupported.value = Boolean(callback());
  208. update();
  209. tryOnMounted(update, sync);
  210. return isSupported;
  211. }
  212. function useClipboard(options = {}) {
  213. const {
  214. navigator = defaultNavigator,
  215. read = false,
  216. source,
  217. copiedDuring = 1500,
  218. legacy = false
  219. } = options;
  220. const events = ["copy", "cut"];
  221. const isClipboardApiSupported = useSupported(() => navigator && "clipboard" in navigator);
  222. const isSupported = vue.computed(() => isClipboardApiSupported.value || legacy);
  223. const text = vue.ref("");
  224. const copied = vue.ref(false);
  225. const timeout = useTimeoutFn(() => copied.value = false, copiedDuring);
  226. function updateText() {
  227. if (isClipboardApiSupported.value) {
  228. navigator.clipboard.readText().then((value) => {
  229. text.value = value;
  230. });
  231. } else {
  232. text.value = legacyRead();
  233. }
  234. }
  235. if (isSupported.value && read) {
  236. for (const event of events)
  237. useEventListener(event, updateText);
  238. }
  239. async function copy(value = resolveUnref(source)) {
  240. if (isSupported.value && value != null) {
  241. if (isClipboardApiSupported.value)
  242. await navigator.clipboard.writeText(value);
  243. else
  244. legacyCopy(value);
  245. text.value = value;
  246. copied.value = true;
  247. timeout.start();
  248. }
  249. }
  250. function legacyCopy(value) {
  251. const ta = document.createElement("textarea");
  252. ta.value = value != null ? value : "";
  253. ta.style.position = "absolute";
  254. ta.style.opacity = "0";
  255. document.body.appendChild(ta);
  256. ta.select();
  257. document.execCommand("copy");
  258. ta.remove();
  259. }
  260. function legacyRead() {
  261. var _a2, _b, _c;
  262. return (_c = (_b = (_a2 = document == null ? void 0 : document.getSelection) == null ? void 0 : _a2.call(document)) == null ? void 0 : _b.toString()) != null ? _c : "";
  263. }
  264. return {
  265. isSupported,
  266. text,
  267. copied,
  268. copy
  269. };
  270. }
  271. const _global = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
  272. const globalKey = "__vueuse_ssr_handlers__";
  273. _global[globalKey] = _global[globalKey] || {};
  274. var SwipeDirection;
  275. (function(SwipeDirection2) {
  276. SwipeDirection2["UP"] = "UP";
  277. SwipeDirection2["RIGHT"] = "RIGHT";
  278. SwipeDirection2["DOWN"] = "DOWN";
  279. SwipeDirection2["LEFT"] = "LEFT";
  280. SwipeDirection2["NONE"] = "NONE";
  281. })(SwipeDirection || (SwipeDirection = {}));
  282. var __defProp = Object.defineProperty;
  283. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  284. var __hasOwnProp = Object.prototype.hasOwnProperty;
  285. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  286. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  287. var __spreadValues = (a, b) => {
  288. for (var prop in b || (b = {}))
  289. if (__hasOwnProp.call(b, prop))
  290. __defNormalProp(a, prop, b[prop]);
  291. if (__getOwnPropSymbols)
  292. for (var prop of __getOwnPropSymbols(b)) {
  293. if (__propIsEnum.call(b, prop))
  294. __defNormalProp(a, prop, b[prop]);
  295. }
  296. return a;
  297. };
  298. const _TransitionPresets = {
  299. easeInSine: [0.12, 0, 0.39, 0],
  300. easeOutSine: [0.61, 1, 0.88, 1],
  301. easeInOutSine: [0.37, 0, 0.63, 1],
  302. easeInQuad: [0.11, 0, 0.5, 0],
  303. easeOutQuad: [0.5, 1, 0.89, 1],
  304. easeInOutQuad: [0.45, 0, 0.55, 1],
  305. easeInCubic: [0.32, 0, 0.67, 0],
  306. easeOutCubic: [0.33, 1, 0.68, 1],
  307. easeInOutCubic: [0.65, 0, 0.35, 1],
  308. easeInQuart: [0.5, 0, 0.75, 0],
  309. easeOutQuart: [0.25, 1, 0.5, 1],
  310. easeInOutQuart: [0.76, 0, 0.24, 1],
  311. easeInQuint: [0.64, 0, 0.78, 0],
  312. easeOutQuint: [0.22, 1, 0.36, 1],
  313. easeInOutQuint: [0.83, 0, 0.17, 1],
  314. easeInExpo: [0.7, 0, 0.84, 0],
  315. easeOutExpo: [0.16, 1, 0.3, 1],
  316. easeInOutExpo: [0.87, 0, 0.13, 1],
  317. easeInCirc: [0.55, 0, 1, 0.45],
  318. easeOutCirc: [0, 0.55, 0.45, 1],
  319. easeInOutCirc: [0.85, 0, 0.15, 1],
  320. easeInBack: [0.36, 0, 0.66, -0.56],
  321. easeOutBack: [0.34, 1.56, 0.64, 1],
  322. easeInOutBack: [0.68, -0.6, 0.32, 1.6]
  323. };
  324. __spreadValues({
  325. linear: identity
  326. }, _TransitionPresets);
  327. const _hoisted_1 = {
  328. key: 0,
  329. class: "fixed top-16 right-4 flex flex-row space-x-2 z-10"
  330. };
  331. const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
  332. __name: "pornhub",
  333. setup(__props) {
  334. const isDownloadingVideo = vue.ref(false);
  335. const isDownloadingCover = vue.ref(false);
  336. const downloadProgress = vue.ref(0);
  337. const isDownloading = vue.ref(false);
  338. vue.onMounted(() => {
  339. console.log("脚本已加载");
  340. console.log(`
  341. ____ _ _ _
  342. | _ \\ ___ _ __ _ __ | | | |_ _| |__
  343. | |_) / _ \\| '__| '_ \\| |_| | | | | '_ \\
  344. | __/ (_) | | | | | | _ | |_| | |_) |
  345. |_| \\___/|_| |_| |_|_| |_|\\__,_|_.__/
  346. `);
  347. });
  348. const videoList = vue.ref([]);
  349. const { copy } = useClipboard();
  350. const getFlashVars = () => {
  351. const flashvarsRegex = /flashvars_\d+/;
  352. const flashvarsKey = Object.keys(_unsafeWindow).find((key) => flashvarsRegex.test(key));
  353. if (flashvarsKey) {
  354. return _unsafeWindow[flashvarsKey];
  355. }
  356. return null;
  357. };
  358. const getVideoInfo = () => {
  359. videoList.value = [];
  360. console.log(videoList.value);
  361. const flashvars = getFlashVars();
  362. if (flashvars) {
  363. const mediaDefinitions = flashvars.mediaDefinitions;
  364. Object.values(mediaDefinitions).forEach((media) => {
  365. if (media.format === "mp4") {
  366. _GM_xmlhttpRequest({
  367. url: media.videoUrl,
  368. method: "GET",
  369. responseType: "json",
  370. onload: (response) => {
  371. response.response.forEach((res) => {
  372. videoList.value.push({
  373. videoUrl: res.videoUrl,
  374. quality: res.quality
  375. });
  376. });
  377. },
  378. ontimeout: () => elementPlus.ElMessage.error("请求视频超时,请稍后再试"),
  379. onerror: () => elementPlus.ElMessage.error("请求视频失败,请稍后再试")
  380. });
  381. }
  382. });
  383. isDownloadingVideo.value = true;
  384. } else {
  385. elementPlus.ElMessage.error("没有找到flashvars");
  386. }
  387. };
  388. const downloadVideo = (video) => {
  389. isDownloading.value = true;
  390. downloadProgress.value = 0;
  391. const xhr = new XMLHttpRequest();
  392. xhr.open("GET", video.videoUrl, true);
  393. xhr.responseType = "blob";
  394. xhr.onprogress = (event) => {
  395. if (event.lengthComputable) {
  396. downloadProgress.value = event.loaded / event.total * 100;
  397. }
  398. };
  399. xhr.onload = () => {
  400. if (xhr.status === 200) {
  401. const blob = xhr.response;
  402. const url = URL.createObjectURL(blob);
  403. const a = document.createElement("a");
  404. a.href = url;
  405. a.download = `video_${video.quality}.mp4`;
  406. document.body.appendChild(a);
  407. a.click();
  408. document.body.removeChild(a);
  409. URL.revokeObjectURL(url);
  410. elementPlus.ElMessage.success(`${video.quality} 质量的视频下载成功`);
  411. } else {
  412. elementPlus.ElMessage.error(`${video.quality} 质量的视频下载失败`);
  413. }
  414. isDownloading.value = false;
  415. };
  416. xhr.onerror = () => {
  417. console.error("下载错误");
  418. elementPlus.ElMessage.error(`${video.quality} 质量的视频下载失败`);
  419. isDownloading.value = false;
  420. };
  421. xhr.send();
  422. };
  423. const downloadCover = useThrottleFn(() => {
  424. if (isDownloadingCover.value) return;
  425. isDownloadingCover.value = true;
  426. const flashvars = getFlashVars();
  427. if (flashvars) {
  428. const coverUrl = flashvars.image_url;
  429. _GM_download({
  430. url: coverUrl,
  431. name: "cover.jpg",
  432. onerror: () => elementPlus.ElMessage.error("封面下载失败"),
  433. ontimeout: () => elementPlus.ElMessage.error("封面下载超时"),
  434. onload: () => elementPlus.ElMessage.success("封面下载成功")
  435. });
  436. }
  437. setTimeout(() => {
  438. isDownloadingCover.value = false;
  439. }, 2e3);
  440. }, 2e3);
  441. const copyVideoLink = (url) => {
  442. copy(url);
  443. elementPlus.ElMessage.success("下载链接已复制到剪贴板");
  444. };
  445. return (_ctx, _cache) => {
  446. return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
  447. getFlashVars() && !isDownloadingVideo.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
  448. vue.createElementVNode("button", {
  449. class: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded shadow",
  450. onClick: getVideoInfo
  451. }, " 下载视频 "),
  452. vue.createElementVNode("button", {
  453. class: "bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded shadow",
  454. onClick: _cache[0] || (_cache[0] = //@ts-ignore
  455. (...args) => vue.unref(downloadCover) && vue.unref(downloadCover)(...args))
  456. }, vue.toDisplayString(isDownloadingCover.value ? "下载中..." : "下载封面"), 1)
  457. ])) : vue.createCommentVNode("", true),
  458. vue.createVNode(vue.unref(elementPlus.ElDialog), {
  459. modelValue: isDownloadingVideo.value,
  460. "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isDownloadingVideo.value = $event),
  461. title: "下载视频",
  462. width: "400px"
  463. }, {
  464. default: vue.withCtx(() => [
  465. vue.createVNode(vue.unref(elementPlus.ElSkeleton), {
  466. loading: videoList.value.length === 0,
  467. "animated:true": ""
  468. }, {
  469. template: vue.withCtx(() => [
  470. (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(4, (i) => {
  471. return vue.createElementVNode("div", {
  472. key: i,
  473. class: "mb-4"
  474. }, [
  475. vue.createVNode(vue.unref(elementPlus.ElSkeletonItem), {
  476. variant: "button",
  477. style: { "width": "100%", "height": "48px" }
  478. })
  479. ]);
  480. }), 64))
  481. ]),
  482. default: vue.withCtx(() => [
  483. !isDownloading.value ? (vue.openBlock(true), vue.createElementBlock(vue.Fragment, { key: 0 }, vue.renderList(videoList.value, (video, index) => {
  484. return vue.openBlock(), vue.createElementBlock("div", {
  485. key: index,
  486. class: "mb-4 flex space-x-2"
  487. }, [
  488. vue.createVNode(vue.unref(elementPlus.ElButton), {
  489. class: vue.normalizeClass([
  490. "flex-grow !py-3 !text-base !font-bold !rounded-lg !transition-colors",
  491. "!bg-[#ff9900] hover:!bg-[#ff6600] !text-black"
  492. ]),
  493. onClick: ($event) => downloadVideo(video)
  494. }, {
  495. default: vue.withCtx(() => [
  496. vue.createTextVNode(vue.toDisplayString(video.quality + "p"), 1)
  497. ]),
  498. _: 2
  499. }, 1032, ["onClick"]),
  500. vue.createVNode(vue.unref(elementPlus.ElButton), {
  501. class: vue.normalizeClass([
  502. "!py-3 !text-base !font-bold !rounded-lg !transition-colors",
  503. "!bg-gray-200 hover:!bg-gray-300 !text-gray-700"
  504. ]),
  505. onClick: ($event) => copyVideoLink(video.videoUrl)
  506. }, {
  507. default: vue.withCtx(() => _cache[2] || (_cache[2] = [
  508. vue.createTextVNode(" 复制链接 ")
  509. ])),
  510. _: 2
  511. }, 1032, ["onClick"])
  512. ]);
  513. }), 128)) : vue.createCommentVNode("", true)
  514. ]),
  515. _: 1
  516. }, 8, ["loading"]),
  517. isDownloading.value ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElProgress), {
  518. key: 0,
  519. percentage: downloadProgress.value,
  520. status: "success",
  521. "stroke-width": 20
  522. }, null, 8, ["percentage"])) : vue.createCommentVNode("", true)
  523. ]),
  524. _: 1
  525. }, 8, ["modelValue"])
  526. ], 64);
  527. };
  528. }
  529. });
  530. const _sfc_main = /* @__PURE__ */ vue.defineComponent({
  531. __name: "App",
  532. setup(__props) {
  533. return (_ctx, _cache) => {
  534. return vue.openBlock(), vue.createBlock(_sfc_main$1);
  535. };
  536. }
  537. });
  538. vue.createApp(_sfc_main).mount(
  539. (() => {
  540. const app = document.createElement("div");
  541. document.body.append(app);
  542. return app;
  543. })()
  544. );
  545.  
  546. })(Vue, ElementPlus);