Sleazy Fork is available in English.

pornHelper

下载PornHub视频和封面

// ==UserScript==
// @name         pornHelper
// @namespace    npm/vite-plugin-monkey
// @version      1.0.0
// @author       Kin
// @description  下载PornHub视频和封面
// @license      MIT
// @icon         https://pornhub.com/favicon.ico
// @homepage     https://github.com/Yorushika-fan/pornHelper
// @match        *://*.pornhub.com/*
// @require      https://cdn.jsdelivr.net/npm/vue@3.5.12/dist/vue.global.prod.js
// @require      https://unpkg.com/vue-demi@latest/lib/index.iife.js
// @require      data:application/javascript,window.Vue%3DVue%3B
// @require      https://cdn.jsdelivr.net/npm/element-plus@2.8.5/dist/index.full.min.js
// @resource     ElementPlus  https://cdn.jsdelivr.net/npm/element-plus@2.8.5/dist/index.full.min.css
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_getResourceText
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// ==/UserScript==

(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))} ");

(function (vue, elementPlus) {
  'use strict';

  const cssLoader = (e) => {
    const t = GM_getResourceText(e);
    return GM_addStyle(t), t;
  };
  cssLoader("ElementPlus");
  var _GM_download = /* @__PURE__ */ (() => typeof GM_download != "undefined" ? GM_download : void 0)();
  var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  var _a;
  const isClient = typeof window !== "undefined";
  const isString = (val) => typeof val === "string";
  const noop = () => {
  };
  isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && /iP(ad|hone|od)/.test(window.navigator.userAgent);
  function resolveUnref(r) {
    return typeof r === "function" ? r() : vue.unref(r);
  }
  function createFilterWrapper(filter, fn) {
    function wrapper(...args) {
      return new Promise((resolve, reject) => {
        Promise.resolve(filter(() => fn.apply(this, args), { fn, thisArg: this, args })).then(resolve).catch(reject);
      });
    }
    return wrapper;
  }
  function throttleFilter(ms, trailing = true, leading = true, rejectOnCancel = false) {
    let lastExec = 0;
    let timer;
    let isLeading = true;
    let lastRejector = noop;
    let lastValue;
    const clear = () => {
      if (timer) {
        clearTimeout(timer);
        timer = void 0;
        lastRejector();
        lastRejector = noop;
      }
    };
    const filter = (_invoke) => {
      const duration = resolveUnref(ms);
      const elapsed = Date.now() - lastExec;
      const invoke = () => {
        return lastValue = _invoke();
      };
      clear();
      if (duration <= 0) {
        lastExec = Date.now();
        return invoke();
      }
      if (elapsed > duration && (leading || !isLeading)) {
        lastExec = Date.now();
        invoke();
      } else if (trailing) {
        lastValue = new Promise((resolve, reject) => {
          lastRejector = rejectOnCancel ? reject : resolve;
          timer = setTimeout(() => {
            lastExec = Date.now();
            isLeading = true;
            resolve(invoke());
            clear();
          }, Math.max(0, duration - elapsed));
        });
      }
      if (!leading && !timer)
        timer = setTimeout(() => isLeading = true, duration);
      isLeading = false;
      return lastValue;
    };
    return filter;
  }
  function identity(arg) {
    return arg;
  }
  function tryOnScopeDispose(fn) {
    if (vue.getCurrentScope()) {
      vue.onScopeDispose(fn);
      return true;
    }
    return false;
  }
  function useThrottleFn(fn, ms = 200, trailing = false, leading = true, rejectOnCancel = false) {
    return createFilterWrapper(throttleFilter(ms, trailing, leading, rejectOnCancel), fn);
  }
  function tryOnMounted(fn, sync = true) {
    if (vue.getCurrentInstance())
      vue.onMounted(fn);
    else if (sync)
      fn();
    else
      vue.nextTick(fn);
  }
  function useTimeoutFn(cb, interval, options = {}) {
    const {
      immediate = true
    } = options;
    const isPending = vue.ref(false);
    let timer = null;
    function clear() {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
    }
    function stop() {
      isPending.value = false;
      clear();
    }
    function start(...args) {
      clear();
      isPending.value = true;
      timer = setTimeout(() => {
        isPending.value = false;
        timer = null;
        cb(...args);
      }, resolveUnref(interval));
    }
    if (immediate) {
      isPending.value = true;
      if (isClient)
        start();
    }
    tryOnScopeDispose(stop);
    return {
      isPending: vue.readonly(isPending),
      start,
      stop
    };
  }
  function unrefElement(elRef) {
    var _a2;
    const plain = resolveUnref(elRef);
    return (_a2 = plain == null ? void 0 : plain.$el) != null ? _a2 : plain;
  }
  const defaultWindow = isClient ? window : void 0;
  const defaultNavigator = isClient ? window.navigator : void 0;
  function useEventListener(...args) {
    let target;
    let events;
    let listeners;
    let options;
    if (isString(args[0]) || Array.isArray(args[0])) {
      [events, listeners, options] = args;
      target = defaultWindow;
    } else {
      [target, events, listeners, options] = args;
    }
    if (!target)
      return noop;
    if (!Array.isArray(events))
      events = [events];
    if (!Array.isArray(listeners))
      listeners = [listeners];
    const cleanups = [];
    const cleanup = () => {
      cleanups.forEach((fn) => fn());
      cleanups.length = 0;
    };
    const register = (el, event, listener, options2) => {
      el.addEventListener(event, listener, options2);
      return () => el.removeEventListener(event, listener, options2);
    };
    const stopWatch = vue.watch(() => [unrefElement(target), resolveUnref(options)], ([el, options2]) => {
      cleanup();
      if (!el)
        return;
      cleanups.push(...events.flatMap((event) => {
        return listeners.map((listener) => register(el, event, listener, options2));
      }));
    }, { immediate: true, flush: "post" });
    const stop = () => {
      stopWatch();
      cleanup();
    };
    tryOnScopeDispose(stop);
    return stop;
  }
  function useSupported(callback, sync = false) {
    const isSupported = vue.ref();
    const update = () => isSupported.value = Boolean(callback());
    update();
    tryOnMounted(update, sync);
    return isSupported;
  }
  function useClipboard(options = {}) {
    const {
      navigator = defaultNavigator,
      read = false,
      source,
      copiedDuring = 1500,
      legacy = false
    } = options;
    const events = ["copy", "cut"];
    const isClipboardApiSupported = useSupported(() => navigator && "clipboard" in navigator);
    const isSupported = vue.computed(() => isClipboardApiSupported.value || legacy);
    const text = vue.ref("");
    const copied = vue.ref(false);
    const timeout = useTimeoutFn(() => copied.value = false, copiedDuring);
    function updateText() {
      if (isClipboardApiSupported.value) {
        navigator.clipboard.readText().then((value) => {
          text.value = value;
        });
      } else {
        text.value = legacyRead();
      }
    }
    if (isSupported.value && read) {
      for (const event of events)
        useEventListener(event, updateText);
    }
    async function copy(value = resolveUnref(source)) {
      if (isSupported.value && value != null) {
        if (isClipboardApiSupported.value)
          await navigator.clipboard.writeText(value);
        else
          legacyCopy(value);
        text.value = value;
        copied.value = true;
        timeout.start();
      }
    }
    function legacyCopy(value) {
      const ta = document.createElement("textarea");
      ta.value = value != null ? value : "";
      ta.style.position = "absolute";
      ta.style.opacity = "0";
      document.body.appendChild(ta);
      ta.select();
      document.execCommand("copy");
      ta.remove();
    }
    function legacyRead() {
      var _a2, _b, _c;
      return (_c = (_b = (_a2 = document == null ? void 0 : document.getSelection) == null ? void 0 : _a2.call(document)) == null ? void 0 : _b.toString()) != null ? _c : "";
    }
    return {
      isSupported,
      text,
      copied,
      copy
    };
  }
  const _global = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
  const globalKey = "__vueuse_ssr_handlers__";
  _global[globalKey] = _global[globalKey] || {};
  var SwipeDirection;
  (function(SwipeDirection2) {
    SwipeDirection2["UP"] = "UP";
    SwipeDirection2["RIGHT"] = "RIGHT";
    SwipeDirection2["DOWN"] = "DOWN";
    SwipeDirection2["LEFT"] = "LEFT";
    SwipeDirection2["NONE"] = "NONE";
  })(SwipeDirection || (SwipeDirection = {}));
  var __defProp = Object.defineProperty;
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __spreadValues = (a, b) => {
    for (var prop in b || (b = {}))
      if (__hasOwnProp.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    if (__getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(b)) {
        if (__propIsEnum.call(b, prop))
          __defNormalProp(a, prop, b[prop]);
      }
    return a;
  };
  const _TransitionPresets = {
    easeInSine: [0.12, 0, 0.39, 0],
    easeOutSine: [0.61, 1, 0.88, 1],
    easeInOutSine: [0.37, 0, 0.63, 1],
    easeInQuad: [0.11, 0, 0.5, 0],
    easeOutQuad: [0.5, 1, 0.89, 1],
    easeInOutQuad: [0.45, 0, 0.55, 1],
    easeInCubic: [0.32, 0, 0.67, 0],
    easeOutCubic: [0.33, 1, 0.68, 1],
    easeInOutCubic: [0.65, 0, 0.35, 1],
    easeInQuart: [0.5, 0, 0.75, 0],
    easeOutQuart: [0.25, 1, 0.5, 1],
    easeInOutQuart: [0.76, 0, 0.24, 1],
    easeInQuint: [0.64, 0, 0.78, 0],
    easeOutQuint: [0.22, 1, 0.36, 1],
    easeInOutQuint: [0.83, 0, 0.17, 1],
    easeInExpo: [0.7, 0, 0.84, 0],
    easeOutExpo: [0.16, 1, 0.3, 1],
    easeInOutExpo: [0.87, 0, 0.13, 1],
    easeInCirc: [0.55, 0, 1, 0.45],
    easeOutCirc: [0, 0.55, 0.45, 1],
    easeInOutCirc: [0.85, 0, 0.15, 1],
    easeInBack: [0.36, 0, 0.66, -0.56],
    easeOutBack: [0.34, 1.56, 0.64, 1],
    easeInOutBack: [0.68, -0.6, 0.32, 1.6]
  };
  __spreadValues({
    linear: identity
  }, _TransitionPresets);
  const _hoisted_1 = {
    key: 0,
    class: "fixed top-16 right-4 flex flex-row space-x-2 z-10"
  };
  const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
    __name: "pornhub",
    setup(__props) {
      const isDownloadingVideo = vue.ref(false);
      const isDownloadingCover = vue.ref(false);
      const downloadProgress = vue.ref(0);
      const isDownloading = vue.ref(false);
      vue.onMounted(() => {
        console.log("脚本已加载");
        console.log(`
    ____                  _   _       _     
   |  _ \\ ___  _ __ _ __ | | | |_   _| |__  
   | |_) / _ \\| '__| '_ \\| |_| | | | | '_ \\ 
   |  __/ (_) | |  | | | |  _  | |_| | |_) |
   |_|   \\___/|_|  |_| |_|_| |_|\\__,_|_.__/ 
  `);
      });
      const videoList = vue.ref([]);
      const { copy } = useClipboard();
      const getFlashVars = () => {
        const flashvarsRegex = /flashvars_\d+/;
        const flashvarsKey = Object.keys(_unsafeWindow).find((key) => flashvarsRegex.test(key));
        if (flashvarsKey) {
          return _unsafeWindow[flashvarsKey];
        }
        return null;
      };
      const getVideoInfo = () => {
        videoList.value = [];
        console.log(videoList.value);
        const flashvars = getFlashVars();
        if (flashvars) {
          const mediaDefinitions = flashvars.mediaDefinitions;
          Object.values(mediaDefinitions).forEach((media) => {
            if (media.format === "mp4") {
              _GM_xmlhttpRequest({
                url: media.videoUrl,
                method: "GET",
                responseType: "json",
                onload: (response) => {
                  response.response.forEach((res) => {
                    videoList.value.push({
                      videoUrl: res.videoUrl,
                      quality: res.quality
                    });
                  });
                },
                ontimeout: () => elementPlus.ElMessage.error("请求视频超时,请稍后再试"),
                onerror: () => elementPlus.ElMessage.error("请求视频失败,请稍后再试")
              });
            }
          });
          isDownloadingVideo.value = true;
        } else {
          elementPlus.ElMessage.error("没有找到flashvars");
        }
      };
      const downloadVideo = (video) => {
        isDownloading.value = true;
        downloadProgress.value = 0;
        const xhr = new XMLHttpRequest();
        xhr.open("GET", video.videoUrl, true);
        xhr.responseType = "blob";
        xhr.onprogress = (event) => {
          if (event.lengthComputable) {
            downloadProgress.value = event.loaded / event.total * 100;
          }
        };
        xhr.onload = () => {
          if (xhr.status === 200) {
            const blob = xhr.response;
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = `video_${video.quality}.mp4`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            elementPlus.ElMessage.success(`${video.quality} 质量的视频下载成功`);
          } else {
            elementPlus.ElMessage.error(`${video.quality} 质量的视频下载失败`);
          }
          isDownloading.value = false;
        };
        xhr.onerror = () => {
          console.error("下载错误");
          elementPlus.ElMessage.error(`${video.quality} 质量的视频下载失败`);
          isDownloading.value = false;
        };
        xhr.send();
      };
      const downloadCover = useThrottleFn(() => {
        if (isDownloadingCover.value) return;
        isDownloadingCover.value = true;
        const flashvars = getFlashVars();
        if (flashvars) {
          const coverUrl = flashvars.image_url;
          _GM_download({
            url: coverUrl,
            name: "cover.jpg",
            onerror: () => elementPlus.ElMessage.error("封面下载失败"),
            ontimeout: () => elementPlus.ElMessage.error("封面下载超时"),
            onload: () => elementPlus.ElMessage.success("封面下载成功")
          });
        }
        setTimeout(() => {
          isDownloadingCover.value = false;
        }, 2e3);
      }, 2e3);
      const copyVideoLink = (url) => {
        copy(url);
        elementPlus.ElMessage.success("下载链接已复制到剪贴板");
      };
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
          getFlashVars() && !isDownloadingVideo.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
            vue.createElementVNode("button", {
              class: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded shadow",
              onClick: getVideoInfo
            }, " 下载视频 "),
            vue.createElementVNode("button", {
              class: "bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded shadow",
              onClick: _cache[0] || (_cache[0] = //@ts-ignore
              (...args) => vue.unref(downloadCover) && vue.unref(downloadCover)(...args))
            }, vue.toDisplayString(isDownloadingCover.value ? "下载中..." : "下载封面"), 1)
          ])) : vue.createCommentVNode("", true),
          vue.createVNode(vue.unref(elementPlus.ElDialog), {
            modelValue: isDownloadingVideo.value,
            "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isDownloadingVideo.value = $event),
            title: "下载视频",
            width: "400px"
          }, {
            default: vue.withCtx(() => [
              vue.createVNode(vue.unref(elementPlus.ElSkeleton), {
                loading: videoList.value.length === 0,
                "animated:true": ""
              }, {
                template: vue.withCtx(() => [
                  (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(4, (i) => {
                    return vue.createElementVNode("div", {
                      key: i,
                      class: "mb-4"
                    }, [
                      vue.createVNode(vue.unref(elementPlus.ElSkeletonItem), {
                        variant: "button",
                        style: { "width": "100%", "height": "48px" }
                      })
                    ]);
                  }), 64))
                ]),
                default: vue.withCtx(() => [
                  !isDownloading.value ? (vue.openBlock(true), vue.createElementBlock(vue.Fragment, { key: 0 }, vue.renderList(videoList.value, (video, index) => {
                    return vue.openBlock(), vue.createElementBlock("div", {
                      key: index,
                      class: "mb-4 flex space-x-2"
                    }, [
                      vue.createVNode(vue.unref(elementPlus.ElButton), {
                        class: vue.normalizeClass([
                          "flex-grow !py-3 !text-base !font-bold !rounded-lg !transition-colors",
                          "!bg-[#ff9900] hover:!bg-[#ff6600] !text-black"
                        ]),
                        onClick: ($event) => downloadVideo(video)
                      }, {
                        default: vue.withCtx(() => [
                          vue.createTextVNode(vue.toDisplayString(video.quality + "p"), 1)
                        ]),
                        _: 2
                      }, 1032, ["onClick"]),
                      vue.createVNode(vue.unref(elementPlus.ElButton), {
                        class: vue.normalizeClass([
                          "!py-3 !text-base !font-bold !rounded-lg !transition-colors",
                          "!bg-gray-200 hover:!bg-gray-300 !text-gray-700"
                        ]),
                        onClick: ($event) => copyVideoLink(video.videoUrl)
                      }, {
                        default: vue.withCtx(() => _cache[2] || (_cache[2] = [
                          vue.createTextVNode(" 复制链接 ")
                        ])),
                        _: 2
                      }, 1032, ["onClick"])
                    ]);
                  }), 128)) : vue.createCommentVNode("", true)
                ]),
                _: 1
              }, 8, ["loading"]),
              isDownloading.value ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElProgress), {
                key: 0,
                percentage: downloadProgress.value,
                status: "success",
                "stroke-width": 20
              }, null, 8, ["percentage"])) : vue.createCommentVNode("", true)
            ]),
            _: 1
          }, 8, ["modelValue"])
        ], 64);
      };
    }
  });
  const _sfc_main = /* @__PURE__ */ vue.defineComponent({
    __name: "App",
    setup(__props) {
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createBlock(_sfc_main$1);
      };
    }
  });
  vue.createApp(_sfc_main).mount(
    (() => {
      const app = document.createElement("div");
      document.body.append(app);
      return app;
    })()
  );

})(Vue, ElementPlus);