// ==UserScript==
// @name 听歌小助手
// @namespace https://github.com/fan0530
// @version 1.1.0
// @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.v2024082001.html
// @connect hifini.com
// @connect gitee.com
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant window.focus
// ==/UserScript==
(e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const t=document.createElement("style");t.textContent=e,document.head.append(t)})(" .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}.btn-container[data-v-341ec50e]{position:fixed;bottom:100px;right:20px}.menu-popper .menu-list[data-v-341ec50e]{list-style:none;margin:0;padding:0}.menu-popper .menu-list li[data-v-341ec50e]{display:flex;flex-direction:column}.menu-popper .menu-list li .menu-item[data-v-341ec50e]{display:flex;align-items:center;justify-content:center;color:#666}.menu-popper .menu-list li .menu-item[data-v-341ec50e]:active{background-color:#80808033} ");
(function (vue, elementPlus, idbKeyval) {
'use strict';
var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : 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 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 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$2 = ["id"];
const _hoisted_2$1 = /* @__PURE__ */ vue.createElementVNode("p", null, "1、打开 Hifini 网站,找到你想听的歌,点击进入具体歌曲的页面。", -1);
const _hoisted_3$1 = /* @__PURE__ */ vue.createElementVNode("p", null, [
/* @__PURE__ */ vue.createTextVNode(" 2、可播放歌曲的帖子会带有播放器组件,如下图所示。 "),
/* @__PURE__ */ vue.createElementVNode("div", { class: "img-wrapper" }, [
/* @__PURE__ */ vue.createElementVNode("img", {
src: "https://cdn.jsdelivr.net/gh/fan0530/music-player/usage/image1.png",
loading: "lazy",
alt: "使用说明"
})
]),
/* @__PURE__ */ vue.createElementVNode("br"),
/* @__PURE__ */ vue.createTextVNode(" 在安装脚本后,脚本会在播放器组件右上角加上“添加至歌单”按钮,如下图。 "),
/* @__PURE__ */ vue.createElementVNode("div", { class: "img-wrapper" }, [
/* @__PURE__ */ vue.createElementVNode("img", {
src: "https://cdn.jsdelivr.net/gh/fan0530/music-player/usage/image2.png",
loading: "lazy",
alt: "使用说明"
})
]),
/* @__PURE__ */ vue.createElementVNode("br"),
/* @__PURE__ */ vue.createTextVNode(" 然后点击该按钮,在播放列表(歌单)页面未打开的情况下,脚本会自动打开播放列表页面,并添加歌曲。 "),
/* @__PURE__ */ vue.createElementVNode("div", { class: "img-wrapper" }, [
/* @__PURE__ */ vue.createElementVNode("img", {
src: "https://cdn.jsdelivr.net/gh/fan0530/music-player/usage/gif1.gif",
loading: "lazy",
alt: "使用说明"
})
]),
/* @__PURE__ */ vue.createElementVNode("br"),
/* @__PURE__ */ vue.createTextVNode(" 如果在播放列表(歌单)页面已打开的情况下点击该按钮,脚本会通知页面去添加歌曲,并在歌曲添加成功后返回提示,然后自行切换到播放列表(歌单)页面就可以看到添加的歌曲。 "),
/* @__PURE__ */ vue.createElementVNode("div", { class: "img-wrapper" }, [
/* @__PURE__ */ vue.createElementVNode("img", {
src: "https://cdn.jsdelivr.net/gh/fan0530/music-player/usage/gif2.gif",
loading: "lazy",
alt: "使用说明"
})
])
], -1);
const _hoisted_4$1 = /* @__PURE__ */ vue.createElementVNode("p", null, "3、添加完你想听的歌曲,你就可以在播放列表(歌单)页面听歌和管理你的歌曲了。", -1);
const _hoisted_5$1 = /* @__PURE__ */ vue.createElementVNode("p", { style: { "font-weight": "bold" } }, "个人建议:打开播放列表(歌单)页面后尽量不要关闭,这样就可以连续播放歌曲而不会中断。", -1);
const _sfc_main$2 = {
__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$2)
]),
default: vue.withCtx(() => [
vue.createVNode(vue.unref(elementPlus.ElScrollbar), { height: "70vh" }, {
default: vue.withCtx(() => [
_hoisted_2$1,
_hoisted_3$1,
_hoisted_4$1,
_hoisted_5$1
]),
_: 1
})
]),
_: 1
}, 8, ["model-value"]);
};
}
};
const _hoisted_1$1 = {
"element-loading-text": "正在打开...",
style: { "height": "108px" }
};
const _sfc_main$1 = {
__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$1, 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 _withScopeId = (n) => (vue.pushScopeId("data-v-341ec50e"), n = n(), vue.popScopeId(), n);
const _hoisted_1 = { style: { "position": "absolute", "top": "5px", "right": "5px" } };
const _hoisted_2 = { style: { "display": "flex", "align-items": "center", "justify-content": "center" } };
const _hoisted_3 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { style: { "margin-left": "8px" } }, "添加至歌单", -1));
const _hoisted_4 = { style: { "display": "flex", "align-items": "center", "justify-content": "center" } };
const _hoisted_5 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { style: { "margin-left": "8px" } }, "添加至", -1));
const _hoisted_6 = { class: "btn-container" };
const _hoisted_7 = { class: "menu-list" };
const _hoisted_8 = ["onClick"];
const _hoisted_9 = { 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;
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) {
audioItem.id = getId();
audioItem.page = _unsafeWindow.location.href;
if (/[\u4E00-\u9FFF]+/ig.test(audioItem.url) && ((_a = audioItem.url) == null ? void 0 : _a.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 = () => {
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 addSong = async (playlistId) => {
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;
}
const audioData = await getAudioData();
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([]);
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 = [];
}
};
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) => {
var _a;
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, [
!((_a = customPlaylist.value) == null ? void 0 : _a.length) ? (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElButton), {
key: 0,
style: { "color": "#515151" },
color: "#ffd448",
onClick: _cache[0] || (_cache[0] = ($event) => addSong("default")),
loading: isAdding.value
}, {
default: vue.withCtx(() => [
vue.createElementVNode("div", _hoisted_2, [
vue.createVNode(vue.unref(AddMusic), {
theme: "outline",
size: "22",
fill: "#666"
}),
_hoisted_3
])
]),
_: 1
}, 8, ["loading"])) : (vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDropdown), {
key: 1,
onCommand: addSong
}, {
dropdown: vue.withCtx(() => [
vue.createVNode(vue.unref(elementPlus.ElDropdownMenu), null, {
default: vue.withCtx(() => [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(customPlaylist.value, (item, index) => {
return vue.openBlock(), vue.createBlock(vue.unref(elementPlus.ElDropdownItem), {
key: index,
command: item.id
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(item.name), 1)
]),
_: 2
}, 1032, ["command"]);
}), 128))
]),
_: 1
})
]),
default: vue.withCtx(() => [
vue.createVNode(vue.unref(elementPlus.ElButton), {
style: { "color": "#515151" },
color: "#ffd448",
loading: isAdding.value
}, {
default: vue.withCtx(() => [
vue.createElementVNode("div", _hoisted_4, [
vue.createVNode(vue.unref(AddMusic), {
theme: "outline",
size: "22",
fill: "#666"
}),
_hoisted_5,
vue.createVNode(vue.unref(Down), {
style: { "margin-left": "8px" },
theme: "outline",
size: "22",
fill: "#666"
})
])
]),
_: 1
}, 8, ["loading"])
]),
_: 1
}))
])
])) : vue.createCommentVNode("", true),
vue.createElementVNode("div", _hoisted_6, [
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_7, [
(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_9, vue.toDisplayString(item.title), 1)
], 8, _hoisted_8),
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$2, {
show: showHelpDialog.value,
"onUpdate:show": _cache[1] || (_cache[1] = ($event) => showHelpDialog.value = $event)
}, null, 8, ["show"]),
vue.createVNode(_sfc_main$1, {
show: showLoading.value,
"onUpdate:show": _cache[2] || (_cache[2] = ($event) => showLoading.value = $event)
}, null, 8, ["show"])
], 64);
};
}
};
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-341ec50e"]]);
const cssLoader = (e) => {
const t = GM_getResourceText(e);
return GM_addStyle(t), t;
};
cssLoader("element-plus/dist/index.css");
vue.createApp(App).use(elementPlus.ElLoading).mount(
(() => {
const app = document.createElement("div");
document.body.append(app);
return app;
})()
);
})(Vue, ElementPlus, idbKeyval);