// ==UserScript== // @name 听歌小助手 // @namespace https://github.com/fan0530 // @version 1.1.6 // @author fanxq // @description 这个脚本主要为Hifini(音乐磁场)网站提供了一个歌曲管理页面(可添加、删除、列表播放歌曲等)以及歌曲下载功能 // @icon https://cdn.jsdelivr.net/gh/fan0530/music-player/favicon.ico // @match https://hifini.com/* // @match https://www.hifini.com/* // @require https://unpkg.com/vue@3.4.19/dist/vue.global.prod.js // @require data:application/javascript,window.Vue%3DVue%3B // @require https://unpkg.com/element-plus@2.5.6/dist/index.full.min.js // @require https://unpkg.com/idb-keyval@6.2.1/dist/umd.js // @resource element-plus/dist/index.css https://unpkg.com/element-plus@2.5.6/dist/index.css // @resource player.html https://cdn.jsdelivr.net/gh/fan0530/music-player@main/index.v2024122201.html // @connect hifini.com // @connect gitee.com // @connect kuwo.cn // @connect 126.net // @connect qq.com // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_info // @grant GM_log // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant window.focus // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/497671/%E5%90%AC%E6%AD%8C%E5%B0%8F%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/497671/%E5%90%AC%E6%AD%8C%E5%B0%8F%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const e=document.createElement("style");e.textContent=t,document.head.append(e)})(" .my-help-dialog{--el-dialog-width: 92% !important;max-width:none;min-width:none}.my-help-dialog .img-wrapper{margin:10px 0;padding:20px;background:#f7f7f7}@media screen and (min-width: 500px){.my-help-dialog{--el-dialog-width: 55% !important;max-width:800px;min-width:500px}}.loading-toast{--el-dialog-width: 140px !important;--el-dialog-border-radius: 10px !important}.loading-toast .el-dialog__header{display:none}.menu-btn[data-v-58148ac7]{height:40px;width:60px;border-radius:0 20px 20px 0}.menu-btn[data-v-58148ac7]:focus{outline:none!important}.menu-list[data-v-58148ac7]{list-style:none;margin:0;padding:0}.menu-list li[data-v-58148ac7]{display:flex;flex-direction:column}.menu-list li .menu-item[data-v-58148ac7]{display:flex;align-items:center;justify-content:center;color:#666;text-decoration:none}.menu-list li .menu-item[data-v-58148ac7]:active,.menu-list li .menu-item[data-v-58148ac7]:hover{background-color:#80808033}.sub-menu[data-v-58148ac7]{list-style:none!important;margin:0!important;padding:0}.sub-menu li[data-v-58148ac7]{box-sizing:border-box;cursor:pointer;color:#666}.sub-menu li a[data-v-58148ac7]{padding:6px;text-decoration:none;width:100%;display:inline-block;box-sizing:border-box}.sub-menu li a[data-v-58148ac7]:active,.sub-menu li a[data-v-58148ac7]:hover{background-color:#80808033}.btn-container[data-v-1c240dd1]{position:fixed;bottom:100px;right:20px}.menu-popper .menu-list[data-v-1c240dd1]{list-style:none;margin:0;padding:0}.menu-popper .menu-list li[data-v-1c240dd1]{display:flex;flex-direction:column}.menu-popper .menu-list li .menu-item[data-v-1c240dd1]{display:flex;align-items:center;justify-content:center;color:#666}.menu-popper .menu-list li .menu-item[data-v-1c240dd1]:active{background-color:#80808033} "); (function (vue, elementPlus, idbKeyval) { 'use strict'; var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)(); var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); var _GM_log = /* @__PURE__ */ (() => typeof GM_log != "undefined" ? GM_log : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); var DEFAULT_ICON_CONFIGS = { size: "1em", strokeWidth: 4, strokeLinecap: "round", strokeLinejoin: "round", rtl: false, theme: "outline", colors: { outline: { fill: "#333", background: "transparent" }, filled: { fill: "#333", background: "#FFF" }, twoTone: { fill: "#333", twoTone: "#2F88FF" }, multiColor: { outStrokeColor: "#333", outFillColor: "#2F88FF", innerStrokeColor: "#FFF", innerFillColor: "#43CCF8" } }, prefix: "i" }; function guid() { return "icon-" + ((1 + Math.random()) * 4294967296 | 0).toString(16).substring(1); } function IconConverter(id, icon, config) { var fill = typeof icon.fill === "string" ? [icon.fill] : icon.fill || []; var colors = []; var theme = icon.theme || config.theme; switch (theme) { case "outline": colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push("none"); colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push("none"); break; case "filled": colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push("#FFF"); colors.push("#FFF"); break; case "two-tone": colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push(typeof fill[1] === "string" ? fill[1] : config.colors.twoTone.twoTone); colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push(typeof fill[1] === "string" ? fill[1] : config.colors.twoTone.twoTone); break; case "multi-color": colors.push(typeof fill[0] === "string" ? fill[0] : "currentColor"); colors.push(typeof fill[1] === "string" ? fill[1] : config.colors.multiColor.outFillColor); colors.push(typeof fill[2] === "string" ? fill[2] : config.colors.multiColor.innerStrokeColor); colors.push(typeof fill[3] === "string" ? fill[3] : config.colors.multiColor.innerFillColor); break; } return { size: icon.size || config.size, strokeWidth: icon.strokeWidth || config.strokeWidth, strokeLinecap: icon.strokeLinecap || config.strokeLinecap, strokeLinejoin: icon.strokeLinejoin || config.strokeLinejoin, colors, id }; } var IconContext = Symbol("icon-context"); function IconWrapper(name, rtl, render) { var options = { name: "icon-" + name, props: ["size", "strokeWidth", "strokeLinecap", "strokeLinejoin", "theme", "fill", "spin"], setup: function setup(props) { var id = guid(); var ICON_CONFIGS = vue.inject(IconContext, DEFAULT_ICON_CONFIGS); return function() { var size = props.size, strokeWidth = props.strokeWidth, strokeLinecap = props.strokeLinecap, strokeLinejoin = props.strokeLinejoin, theme = props.theme, fill = props.fill, spin = props.spin; var svgProps = IconConverter(id, { size, strokeWidth, strokeLinecap, strokeLinejoin, theme, fill }, ICON_CONFIGS); var cls = [ICON_CONFIGS.prefix + "-icon"]; cls.push(ICON_CONFIGS.prefix + "-icon-" + name); if (rtl && ICON_CONFIGS.rtl) { cls.push(ICON_CONFIGS.prefix + "-icon-rtl"); } if (spin) { cls.push(ICON_CONFIGS.prefix + "-icon-spin"); } return vue.createVNode("span", { "class": cls.join(" ") }, [render(svgProps)]); }; } }; return options; } const AddMusic = IconWrapper("add-music", true, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("path", { "d": "M24 44C12.9543 44 4 35.0457 4 24C4 12.9543 12.9543 4 24 4C35.0457 4 44 12.9543 44 24", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M20 24V17.0718L26 20.5359L32 24L26 27.4641L20 30.9282V24Z", "fill": props.colors[1], "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M37.0508 32L37.0508 42", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M42 36.9497L32 36.9497", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null)]); }); const Down = IconWrapper("down", false, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("path", { "d": "M36 18L24 30L12 18", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null)]); }); const DownTwo = IconWrapper("down-two", false, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("path", { "d": "M5 24L24 42L43 24H31V6H17V24H5Z", "fill": props.colors[1], "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null)]); }); const Help = IconWrapper("help", true, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("path", { "d": "M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z", "fill": props.colors[1], "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M24 28.6248V24.6248C27.3137 24.6248 30 21.9385 30 18.6248C30 15.3111 27.3137 12.6248 24 12.6248C20.6863 12.6248 18 15.3111 18 18.6248", "stroke": props.colors[2], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "fill-rule": "evenodd", "clip-rule": "evenodd", "d": "M24 37.6248C25.3807 37.6248 26.5 36.5055 26.5 35.1248C26.5 33.7441 25.3807 32.6248 24 32.6248C22.6193 32.6248 21.5 33.7441 21.5 35.1248C21.5 36.5055 22.6193 37.6248 24 37.6248Z", "fill": props.colors[2] }, null)]); }); const More = IconWrapper("more", false, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("circle", { "cx": "12", "cy": "24", "r": "3", "fill": props.colors[0] }, null), vue.createVNode("circle", { "cx": "24", "cy": "24", "r": "3", "fill": props.colors[0] }, null), vue.createVNode("circle", { "cx": "36", "cy": "24", "r": "3", "fill": props.colors[0] }, null)]); }); const Record = IconWrapper("record", true, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("rect", { "x": "5", "y": "18", "width": "38", "height": "24", "rx": "2", "fill": props.colors[1], "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M8 12H40", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M15 6L33 6", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M26 24V30", "stroke": props.colors[2], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M18 32.7491C18 31.2308 19.2894 30 20.88 30H26V33.2509C26 34.7692 24.7106 36 23.12 36H20.88C19.2894 36 18 34.7692 18 33.2509V32.7491Z", "stroke": props.colors[2], "stroke-width": props.strokeWidth, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M31 25L26 24", "stroke": props.colors[2], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null)]); }); const RecordPlayer = IconWrapper("record-player", true, function(props) { return vue.createVNode("svg", { "width": props.size, "height": props.size, "viewBox": "0 0 48 48", "fill": "none" }, [vue.createVNode("rect", { "x": "5", "y": "8", "width": "38", "height": "32", "rx": "2", "stroke": props.colors[0], "stroke-width": props.strokeWidth }, null), vue.createVNode("path", { "d": "M13 8V40", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("circle", { "cx": "28", "cy": "24", "r": "9", "fill": props.colors[1], "stroke": props.colors[0], "stroke-width": props.strokeWidth }, null), vue.createVNode("circle", { "cx": "28", "cy": "24", "r": "3", "fill": props.colors[2] }, null), vue.createVNode("path", { "d": "M5 16H13", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M5 24H13", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null), vue.createVNode("path", { "d": "M5 32H13", "stroke": props.colors[0], "stroke-width": props.strokeWidth, "stroke-linecap": props.strokeLinecap, "stroke-linejoin": props.strokeLinejoin }, null)]); }); const _hoisted_1$4 = ["id"]; const _hoisted_2$2 = /* @__PURE__ */ vue.createElementVNode("p", null, [ /* @__PURE__ */ vue.createTextVNode("具体使用说明请查看这篇文章: "), /* @__PURE__ */ vue.createElementVNode("a", { target: "_blank", href: "https://mp.weixin.qq.com/s?__biz=Mzg2NDgzMjU1OA==&mid=2247483945&idx=1&sn=cdf6194c875eeeb143c51a7a9cf8f520&chksm=ce621f68f915967e712d03de99b81e6f64f3e28ca4b970815e78d388b4ad76d04d6fd40e69e2#rd" }, " 有用这颗“黑凤梨”听歌的朋友吗?我写了个油猴脚本送给你 ") ], -1); const _hoisted_3$2 = /* @__PURE__ */ vue.createElementVNode("p", null, [ /* @__PURE__ */ vue.createTextVNode("如果在使用该脚本的过程遇到了问题,或者觉得有需要改善的地方,可以在 "), /* @__PURE__ */ vue.createElementVNode("a", { target: "_blank", href: "https://greasyfork.org/zh-CN/scripts/497671-%E5%90%AC%E6%AD%8C%E5%B0%8F%E5%8A%A9%E6%89%8B/feedback" }, " 此处(https://greasyfork.org/zh-CN/scripts/497671-%E5%90%AC%E6%AD%8C%E5%B0%8F%E5%8A%A9%E6%89%8B/feedback) "), /* @__PURE__ */ vue.createTextVNode(" 反馈。 ") ], -1); const _hoisted_4$2 = /* @__PURE__ */ vue.createElementVNode("p", null, "最后,如果这个脚本对你有点帮助的话,可以考虑请我喝瓶快乐水,你的支持将给我动力持续去维护好这个脚本。", -1); const _hoisted_5$2 = /* @__PURE__ */ vue.createElementVNode("p", null, [ /* @__PURE__ */ vue.createElementVNode("img", { src: "https://gitee.com/fanxiqian/music-player/raw/master/code.jpg", alt: "赞赏码", style: { "display": "block", "margin": "0 auto", "width": "100%", "max-width": "320px", "height": "auto" } }) ], -1); const _sfc_main$4 = { __name: "HelpDialog", props: { show: { type: Boolean, default: false } }, emits: ["update:show"], setup(__props, { emit: __emit }) { const emits = __emit; const updateShow = (visible) => { emits("update:show", visible); }; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDialog), { "model-value": __props.show, onClose: _cache[0] || (_cache[0] = ($event) => updateShow(false)), class: "my-help-dialog", "align-center": "" }, { header: vue.withCtx(({ titleId, titleClass }) => [ vue.createElementVNode("h4", { id: titleId, class: vue.normalizeClass(titleClass) }, "使用说明", 10, _hoisted_1$4) ]), default: vue.withCtx(() => [ vue.createVNode(vue.unref(elementPlus.ElScrollbar), { height: "70vh" }, { default: vue.withCtx(() => [ _hoisted_2$2, _hoisted_3$2, _hoisted_4$2, _hoisted_5$2 ]), _: 1 }) ]), _: 1 }, 8, ["model-value"]); }; } }; const _hoisted_1$3 = { "element-loading-text": "正在打开...", style: { "height": "108px" } }; const _sfc_main$3 = { __name: "LoadingToast", props: { show: { type: Boolean, default: false } }, emits: ["update:show"], setup(__props, { emit: __emit }) { const emits = __emit; const updateShow = (visible) => { emits("update:show", visible); }; return (_ctx, _cache) => { const _directive_loading = vue.resolveDirective("loading"); return vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDialog), { "model-value": __props.show, onClose: _cache[0] || (_cache[0] = ($event) => updateShow(false)), "align-center": "", "show-close": false, modal: false, "close-on-click-modal": false, "close-on-press-escape": false, class: "loading-toast" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$3, null, 512), [ [_directive_loading, true] ]) ]), _: 1 }, 8, ["model-value"]); }; } }; const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const _hoisted_1$2 = { class: "menu-list" }; const _hoisted_2$1 = { class: "menu-item", href: "javascript: void 0;" }; const _hoisted_3$1 = { style: { "margin-left": "12px" } }; const _hoisted_4$1 = ["onClick"]; const _hoisted_5$1 = { href: "javascript: void 0;" }; const _hoisted_6 = ["onClick"]; const _hoisted_7 = { style: { "margin-left": "12px" } }; const _sfc_main$2 = { __name: "BaseDropdownMenu", props: { menus: { type: Array, default: () => [] } }, setup(__props) { const subMenuRef = vue.ref(null); const handleSubMenuItemClick = (item) => { var _a, _b; (_a = item.handler) == null ? void 0 : _a.call(item); (_b = subMenuRef.value) == null ? void 0 : _b.hide(); }; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElPopover), { placement: "bottom", trigger: "click", "show-arrow": false }, { reference: vue.withCtx(() => [ vue.renderSlot(_ctx.$slots, "reference", {}, () => [ vue.createVNode(vue.unref(elementPlus.ElButton), { class: "menu-btn" }, { default: vue.withCtx(() => [ vue.createVNode(vue.unref(Record), { theme: "two-tone", size: "30", fill: ["#409c3f", "#ffd448"], strokeWidth: 3 }) ]), _: 1 }) ], true) ]), default: vue.withCtx(() => [ vue.createElementVNode("ul", _hoisted_1$2, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.menus, (item, index) => { var _a; return vue.openBlock(), vue.createElementBlock("li", { key: item.name }, [ ((_a = item.subMenus) == null ? void 0 : _a.length) ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElPopover), { key: 0, ref_for: true, ref: (el) => subMenuRef.value = el, placement: "right-start", offset: 20, "show-arrow": false }, { reference: vue.withCtx(() => [ vue.createElementVNode("div", null, [ vue.createElementVNode("a", _hoisted_2$1, [ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(item.icon), { size: "22", strokeWidth: 3, fill: "#666", style: { "line-height": "1" } })), vue.createElementVNode("span", _hoisted_3$1, vue.toDisplayString(item.title), 1) ]), index !== __props.menus.length - 1 ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDivider), { key: 0, style: { "margin": "10px 0" } })) : vue.createCommentVNode("", true) ]) ]), default: vue.withCtx(() => [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(item.subMenus, (subItem, subIndex) => { return vue.openBlock(), vue.createElementBlock("ul", { key: subIndex, class: "sub-menu" }, [ vue.createElementVNode("li", { onClick: ($event) => handleSubMenuItemClick(subItem) }, [ vue.createElementVNode("a", _hoisted_5$1, vue.toDisplayString(subItem.name), 1) ], 8, _hoisted_4$1) ]); }), 128)) ]), _: 2 }, 1536)) : (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [ vue.createElementVNode("a", { class: "menu-item", onClick: () => { var _a2; return (_a2 = item.handler) == null ? void 0 : _a2.call(item); }, href: "javascript: void 0;" }, [ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(item.icon), { size: "22", strokeWidth: 3, fill: "#666", style: { "line-height": "1" } })), vue.createElementVNode("span", _hoisted_7, vue.toDisplayString(item.title), 1) ], 8, _hoisted_6), index !== __props.menus.length - 1 ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDivider), { key: 0, style: { "margin": "10px 0" } })) : vue.createCommentVNode("", true) ], 64)) ]); }), 128)) ]) ]), _: 3 }); }; } }; const BaseDropdownMenu = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-58148ac7"]]); const _hoisted_1$1 = { style: { "display": "flex", "align-items": "center", "justify-content": "center" } }; const _sfc_main$1 = { __name: "BaseDropdown", props: { customPlaylist: { type: Array, default: () => [] } }, emits: ["add", "download"], setup(__props, { emit: __emit }) { const emits = __emit; const props = __props; const menus = vue.computed(() => { return [ { name: "add", title: "添加至...", icon: vue.markRaw(AddMusic), subMenus: props.customPlaylist.map((p) => { return { id: p.id, name: p.name, handler: () => { emits("add", p.id); } }; }) }, { name: "download", title: "下载歌曲", icon: vue.markRaw(DownTwo), handler: () => { emits("download"); } } ]; }); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(BaseDropdownMenu, { menus: menus.value }, { reference: vue.withCtx(() => [ vue.renderSlot(_ctx.$slots, "reference", {}, () => [ vue.createVNode(vue.unref(elementPlus.ElButton), { style: { "color": "#515151" }, color: "#ffd448" }, { default: vue.withCtx(() => [ vue.createElementVNode("div", _hoisted_1$1, [ vue.createVNode(vue.unref(AddMusic), { theme: "outline", size: "22", fill: "#666" }), vue.createVNode(vue.unref(Down), { style: { "margin-left": "8px" }, theme: "outline", size: "22", fill: "#666" }) ]) ]), _: 1 }) ]) ]), _: 3 }, 8, ["menus"]); }; } }; const _hoisted_1 = { style: { "position": "absolute", "top": "5px", "right": "5px" } }; const _hoisted_2 = { class: "btn-container" }; const _hoisted_3 = { class: "menu-list" }; const _hoisted_4 = ["onClick"]; const _hoisted_5 = { style: { "margin-left": "12px" } }; const _sfc_main = { __name: "App", setup(__props) { const isPlayerExisted = vue.ref(false); const showHelpDialog = vue.ref(false); const isAdding = vue.ref(false); const showLoading = vue.ref(false); let targetWindow = null; const msgHub = []; const menus = vue.ref([ { name: "usage", title: "使用说明", icon: vue.markRaw(Help), handler: () => { showHelpDialog.value = true; } }, { name: "player", title: "打开歌单", icon: vue.markRaw(RecordPlayer), handler: () => { if (!showLoading.value) { showLoading.value = true; } openPlayer(); } } ]); const requestAudioBlobData = (url) => { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "GET", url, headers: { "referer": "https://hifini.com/" }, responseType: "blob", onload: function(res) { if (!(res == null ? void 0 : res.response)) { resolve(null); } resolve(res.response); }, onerror: function(err) { resolve(null); } }); }); }; const getAudioData = async () => { var _a, _b; let audioItem = null; const targetScript = Array.from(_unsafeWindow.document.querySelectorAll("script")).filter((x) => x.innerHTML).find((x) => x.innerHTML.indexOf("APlayer") !== -1); if (targetScript && targetScript.innerHTML) { const code = targetScript.innerHTML; const matches = code.match(/\[([\s\S]*?)\]/igm); if (matches && matches.length) { const musicInfo = matches[0]; const func = new Function(`let a = ${musicInfo}; return a;`); const audioList = func(); if (audioList && audioList.length) { audioItem = audioList[0]; } } } if (audioItem) { if (((_a = audioItem.url) == null ? void 0 : _a.indexOf("music/helloworld.mp3")) !== -1) { elementPlus.ElNotification({ title: "提示", message: "请先登录 Hifini", type: "warning" }); throw new Error("请先登录 Hifini"); } audioItem.id = getId(); audioItem.page = _unsafeWindow.location.href; if (/[\u4E00-\u9FFF]+/ig.test(audioItem.url) && ((_b = audioItem.url) == null ? void 0 : _b.startsWith("https"))) { const res = await requestAudioBlobData(audioItem.url); if (res) { audioItem.url = URL.createObjectURL(res); audioItem.storeKey = `no.${audioItem.id}`; idbKeyval.set(audioItem.storeKey, res).catch((err) => { }); } } } return audioItem; }; const getCacheId = () => { let id = null; return () => { if (!id) { id = Date.now(); const result = /(\d+)/.exec(_unsafeWindow.location.pathname); if (result) { id = result[0]; } } return id; }; }; const getId = getCacheId(); const requestPlayerHtmlContent = () => { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "GET", // url: 'https://gitee.com/fanxiqian/music-player/raw/master/index.txt', url: "https://gitee.com/fanxiqian/music-player/raw/master/home", onload: function(response) { resolve(response.responseText); }, onerror: function(err) { resolve(); } }); }); }; const openPlayerPage = async () => { const playerHtmlFromResource = _GM_getResourceText("player.html"); let fileContent = []; if (playerHtmlFromResource) { fileContent = [playerHtmlFromResource]; } else { const content = await requestPlayerHtmlContent(); if (!content) { elementPlus.ElNotification({ title: "提示", message: "获取播放列表页面出错了,请刷新页面重试", type: "error" }); return; } fileContent = [content]; } const playerBlob = new Blob(fileContent, { type: "text/html" }); const url = URL.createObjectURL(playerBlob); if (!targetWindow) { targetWindow = _unsafeWindow.open(url); } else { if (targetWindow.closed) { targetWindow = _unsafeWindow.open(url); } } targetWindow.focus(); }; const openPlayer = () => { var _a; if (((_a = _unsafeWindow.document.cookie) == null ? void 0 : _a.indexOf("bbs_token")) === -1) { elementPlus.ElNotification({ title: "提示", message: "请先登录 Hifini", type: "warning" }); showLoading.value = false; return; } const channelName = _unsafeWindow.localStorage.getItem("channel"); if (channelName) { if (targetWindow) { targetWindow.focus(); showLoading.value = false; return; } if (!channel) { channel = new BroadcastChannel(channelName); channel.addEventListener("message", (e) => { if (e.data && e.data.from === "player") { msgHub.push(e.data); if (e.data.msg === "heartbeat") { return; } elementPlus.ElNotification({ title: "提示", message: e.data.code === 200 ? "歌曲添加成功" : e.data.msg, type: e.data.code === 200 ? "success" : "warning" }); } }); } const msgId = Date.now(); channel.postMessage({ msg: "heartbeat", msgId }); setTimeout(() => { const idx = msgHub.findIndex((x) => x.msgId == msgId); if (idx !== -1) { msgHub.splice(idx, 1); elementPlus.ElNotification({ title: "提示", message: `歌单页面已存在,请在浏览器标签页或者窗口中查找看看`, type: "info" }); } else { _unsafeWindow.localStorage.removeItem("channel"); channel.close(); channel = null; openPlayer(); } showLoading.value = false; }, 3e3); return; } openPlayerPage(); showLoading.value = false; }; const postAudioDataToPlayer = async (playlistId) => { await openPlayerPage(); if (!targetWindow) { return; } const audioData = await getAudioData(); setTimeout(() => { isAdding.value = false; targetWindow.postMessage({ ...audioData, msgId: Date.now(), playlistId }, "*"); }, 1200); }; const isSongExisted = (id, playlistId) => { var _a, _b; let isExisted = false; try { const storeData = JSON.parse(_unsafeWindow.localStorage.getItem("hifini-helper")); const tPlaylist = (_a = storeData == null ? void 0 : storeData.customPlaylist) == null ? void 0 : _a.find((x) => x.id === playlistId); if ((_b = tPlaylist == null ? void 0 : tPlaylist.songIdList) == null ? void 0 : _b.includes(id)) { isExisted = true; } } catch (error) { } return isExisted; }; let channel = null; const getFileTypeByMagicNumber = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsArrayBuffer(file.slice(0, 12)); reader.onload = function(event) { const data = new Uint8Array(event.target.result); const magicNumber = Array.from(data).map((byte) => byte.toString(16).padStart(2, "0")).join(" "); const audioMagicNumbers = { "49 44 33": "mp3", // ID3标签 "ff fb": "mp3", // MPEG-1 Layer 3 "ff f3": "mp3", // MPEG-2.5 Layer 3 "52 49 46 46": "wav", // RIFF "66 4c 61 43": "flac", // fLaC "66 74 79 70 6d 69 66 31": "m4a", // ftypmif1 "66 74 79 70 6d 64 61 74": "m4a", // ftypmdat "46 4f 52 4d": "aiff" // FORM }; for (const [magic, type] of Object.entries(audioMagicNumbers)) { if (magicNumber.startsWith(magic)) { return resolve(type); } } resolve(""); }; reader.onerror = function(error) { reject(error); }; }); }; const downloadSong = async () => { const audioData = await getAudioData(); if (!(audioData == null ? void 0 : audioData.url)) { elementPlus.ElNotification({ title: "提示", message: `获取下载链接失败`, type: "info" }); } let url = audioData.url; if (url == null ? void 0 : url.startsWith("blob:")) { const a = document.createElement("a"); a.href = url; a.download = `${audioData.title}-${audioData.author}`; a.style.display = "none"; document.body.appendChild(a); a.click(); a.remove(); return; } if (!(url == null ? void 0 : url.startsWith("http"))) { url = `${location.origin}/${url}`; } _GM_xmlhttpRequest({ method: "GET", url, responseType: "blob", onload: async function(res) { const a = document.createElement("a"); a.href = URL.createObjectURL(res.response); let downloadFileName = `${audioData.title}-${audioData.author}`; try { const extName = await getFileTypeByMagicNumber(res.response); if (extName) { downloadFileName += `.${extName}`; } } catch (error) { } a.download = downloadFileName; a.style.display = "none"; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); }, onerror: function(err) { } }); }; const addSong = async (playlistId) => { const audioData = await getAudioData(); const id = getId(); if (isSongExisted(id, playlistId)) { elementPlus.ElNotification({ title: "提示", message: "歌曲已存在,请勿重复添加!", type: "warning" }); return; } const channelName = _unsafeWindow.localStorage.getItem("channel"); if (channelName) { isAdding.value = true; if (!channel) { channel = new BroadcastChannel(channelName); channel.addEventListener("message", (e) => { if (e.data && e.data.from === "player") { isAdding.value = false; msgHub.push(e.data); if (e.data.msg === "heartbeat") { return; } elementPlus.ElNotification({ title: "提示", message: e.data.code === 200 ? "歌曲添加成功" : e.data.msg, type: e.data.code === 200 ? "success" : "warning" }); } }); } const audioData2 = await getAudioData(); const msgId = Date.now(); channel.postMessage({ ...audioData2, msgId, playlistId }); setTimeout(() => { isAdding.value = false; const idx = msgHub.findIndex((x) => x.msgId == msgId); if (idx !== -1) { msgHub.splice(idx, 1); } else { _unsafeWindow.localStorage.removeItem("channel"); channel.close(); channel = null; postAudioDataToPlayer(playlistId); } }, 3e3); return; } await openPlayerPage(); if (!targetWindow) { return; } setTimeout(() => { isAdding.value = false; targetWindow.postMessage({ ...audioData, msgId: Date.now(), playlistId }, "*"); }, 1200); }; _unsafeWindow.addEventListener("message", (e) => { if (e.data === "focus") { window.focus(); } }); const customPlaylist = vue.ref([{ id: "default", name: "默认歌单" }]); const setCustomPlaylist = () => { var _a; const storeData = JSON.parse(_unsafeWindow.localStorage.getItem("hifini-helper")); const tCustomPlaylist = (_a = storeData == null ? void 0 : storeData.customPlaylist) == null ? void 0 : _a.map((x) => ({ id: x.id, name: x.name })); if ((tCustomPlaylist == null ? void 0 : tCustomPlaylist.length) > 1) { customPlaylist.value = tCustomPlaylist; } else { customPlaylist.value = [{ id: "default", name: "默认歌单" }]; } }; const main = () => { const aplayerElement = document.querySelector(".aplayer"); if (aplayerElement) { isPlayerExisted.value = true; aplayerElement.style.position = "relative"; } setCustomPlaylist(); _unsafeWindow.addEventListener("storage", (event) => { var _a; if ((_a = event.url) == null ? void 0 : _a.startsWith("blob:")) { setCustomPlaylist(); } }); }; main(); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [ isPlayerExisted.value ? (vue.openBlock(), vue.createBlock(vue.Teleport, { key: 0, to: ".aplayer" }, [ vue.createElementVNode("div", _hoisted_1, [ vue.createVNode(_sfc_main$1, { customPlaylist: customPlaylist.value, onAdd: addSong, onDownload: downloadSong }, { reference: vue.withCtx(() => [ vue.createVNode(vue.unref(elementPlus.ElButton), { circle: "", style: { "color": "#515151" }, color: "#ffd448", size: "small" }, { default: vue.withCtx(() => [ vue.createVNode(vue.unref(More), { theme: "filled", size: "16", fill: "#515151" }) ]), _: 1 }) ]), _: 1 }, 8, ["customPlaylist"]) ]) ])) : vue.createCommentVNode("", true), vue.createElementVNode("div", _hoisted_2, [ vue.createVNode(vue.unref(elementPlus.ElPopover), { placement: "top-end", trigger: "click", "popper-class": "menu-popper" }, { reference: vue.withCtx(() => [ vue.createVNode(vue.unref(elementPlus.ElButton), { style: { "width": "40px", "height": "40px" }, circle: "" }, { default: vue.withCtx(() => [ vue.createVNode(vue.unref(Record), { theme: "two-tone", size: "30", fill: ["#409c3f", "#ffd448"], strokeWidth: 3 }) ]), _: 1 }) ]), default: vue.withCtx(() => [ vue.createElementVNode("ul", _hoisted_3, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(menus.value, (item, index) => { return vue.openBlock(), vue.createElementBlock("li", { key: item.name }, [ vue.createElementVNode("a", { class: "menu-item", onClick: () => item.handler(), href: "javascript: void 0;" }, [ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(item.icon), { size: "22", strokeWidth: 3, fill: "#666", style: { "line-height": "1" } })), vue.createElementVNode("span", _hoisted_5, vue.toDisplayString(item.title), 1) ], 8, _hoisted_4), index !== menus.value.length - 1 ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDivider), { key: 0, style: { "margin": "10px 0" } })) : vue.createCommentVNode("", true) ]); }), 128)) ]) ]), _: 1 }) ]), vue.createVNode(_sfc_main$4, { show: showHelpDialog.value, "onUpdate:show": _cache[0] || (_cache[0] = ($event) => showHelpDialog.value = $event) }, null, 8, ["show"]), vue.createVNode(_sfc_main$3, { show: showLoading.value, "onUpdate:show": _cache[1] || (_cache[1] = ($event) => showLoading.value = $event) }, null, 8, ["show"]) ], 64); }; } }; const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-1c240dd1"]]); const cssLoader = (e) => { const t = GM_getResourceText(e); return GM_addStyle(t), t; }; cssLoader("element-plus/dist/index.css"); _GM_log(`version: ${_GM_info.script.version}`); vue.createApp(App).use(elementPlus.ElLoading).mount( (() => { const app = document.createElement("div"); document.body.append(app); return app; })() ); })(Vue, ElementPlus, idbKeyval);