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);