UNPKG

koishi-plugin-music-link

Version:

/*音乐下载*/🎵搜索音乐资源🤩提供QQ、网易云平台的音乐下载,付费的也可以欸?[点我查看使用方法](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/music-link)

1,292 lines (1,266 loc) 94.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, inject: () => inject, logger: () => logger, name: () => name, reusable: () => reusable, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var import_promises = __toESM(require("node:fs/promises")); var import_node_crypto = __toESM(require("node:crypto")); var import_node_path = __toESM(require("node:path")); var import_node_url = __toESM(require("node:url")); var name = "music-link"; var reusable = true; var logger = new import_koishi.Logger("music-link"); var inject = { required: ["http", "i18n"], optional: ["puppeteer"] }; var usage = ` <hr> <details> <summary><h3>使用方法 (点击展开)</h3></summary> <p>安装并配置插件后,使用下述命令搜索和下载音乐:</p> <hr> <h3>使用星之阁API搜索QQ、网易云音乐</h3> <pre><code>下载音乐 [keywords]</code></pre> <p><b>(不推荐)</b> 星之阁API,需要加群申请API Key,且API Key可能存在失效风险。支持QQ音乐和网易云音乐,速度较慢,稳定性一般。</p> <hr> <h3>使用星之阁-酷狗API搜索酷狗音乐</h3> <pre><code>酷狗音乐 [keywords]</code></pre> <p><b>(不推荐)</b> 星之阁-酷狗API,需要加群申请API Key,且API Key可能存在失效风险。仅支持酷狗音乐,速度较慢,稳定性一般。</p> <hr> <h3>使用music.gdstudio.xyz网站搜索各大音乐平台</h3> <pre><code>歌曲搜索 [keywords]</code></pre> <p><b>(比较推荐)</b> music.gdstudio.xyz 网站,无需API Key,但需要 <b>puppeteer</b> 服务支持进行网页爬取,速度还行。默认使用网易云音乐搜索,支持多平台选择。</p> <hr> <h3>使用api.injahow.cn网站搜索网易云音乐</h3> <pre><code>网易点歌 [歌曲名称/歌曲ID]</code></pre> <p><b>(很推荐)</b> api.injahow.cn 网站,API请求快速且稳定,无需 puppeteer 服务,推荐QQ官方机器人使用此后端,使用这个后端VIP歌曲只能听45秒,但这个指令还有一个后端可以都听。很好用哦<b>仅支持网易云音乐</b>,可以通过歌曲名称或歌曲ID进行搜索。</p> <hr> <h3>使用dev.iw233.cn网站搜索网易云音乐</h3> <pre><code>音乐搜索器 [keywords]</code></pre> <p><b>(推荐)</b> dev.iw233.cn 网站,无需API Key,但需要 <b>puppeteer</b> 服务支持进行网页爬取,速度较慢。支持网易云音乐搜索。</p> <hr> <h3>使用api.dragonlongzhu.cn网站API搜索音乐</h3> <pre><code>龙珠搜索 [keywords]</code></pre> <p><b>(一般推荐)</b> api.dragonlongzhu.cn 网站的点歌API。支持多平台音乐搜索。</p> <hr> </details> --- <h3>如何返回语音/视频/群文件消息</h3> <p>可以修改对应指令的<code>返回字段表</code>中的 <code>下载链接</code> 对应的 <code>字段发送类型</code> 字段, 把 <code>text</code> 更改为 <code>audio</code> 就是返回 语音, 改为 <code>video</code> 就是返回 视频消息, 改为 <code>file</code> 就是返回 群文件。</p> <hr> <p>⚠️需要注意的是,当配置返回格式为音频/视频的时候,请自行检查是否安装了 <code>silk</code>、<code>ffmpeg</code> 等服务。</p> <p>⚠️如果你选择了 <code>file</code> 类型,请确保平台支持!目前仅实测了 <code>onebot</code> 平台的部分协议端支持!</p> <hr> <h3>使用 <code>-n 1</code> 直接返回内容</h3> <p>在使用命令时,可以通过添加 <code>-n 1</code> 选项直接返回指定序号的歌曲内容。这对于快速获取特定歌曲非常有用。</p> <p>例如,使用以下命令可以直接获取第一首歌曲的详细信息:</p> <pre><code>歌曲搜索 -n 1 蔚蓝档案</code></pre> --- ## 重要提示⚠️ ### 目前 星之阁API的key已经失效,如需使用请自行前往注册 ### 目前 推荐使用<code>api.injahow.cn(网易云点歌)</code>的服务,请确保<code>puppeteer</code>服务可用 --- | 后端推荐度 | 名称 | 备注 | | :--------: | :-------------------------------: | :---: | | **ⅰ** | \`api.injahow.cn\` (歌曲搜索) | 较高 | | **ⅱ** | \`dev.iw233.cn\` (音乐搜索器) | 中等 | | *......* | 其他 | 中等 | | **ⅳ** | \`星之阁API\` (下载音乐/酷狗音乐) | 较低 | --- 目前基本QQ音乐都死翘翘了 (腾讯太小气了 `; var command1_return_qqdata_Field_default = [ { "data": "songname", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "subtitle", "describe": "标题", "type": "text", "enable": false }, { "data": "name", "describe": "歌手", "type": "text", "enable": true }, { "data": "album", "describe": "专辑", "type": "text", "enable": false }, { "data": "pay", "describe": "付费情况", "type": "text", "enable": false }, { "data": "song_type", "describe": "歌曲类型", "type": "text", "enable": false }, { "data": "type", "describe": "类型", "type": "text", "enable": false }, { "data": "songid", "describe": "歌曲ID", "type": "text", "enable": false }, { "data": "mid", "describe": "mid", "type": "text", "enable": false }, { "data": "time", "describe": "发行时间", "type": "text", "enable": false }, { "data": "bpm", "describe": "bpm", "type": "text", "enable": false }, { "data": "quality", "describe": "音质", "type": "text", "enable": true }, { "data": "interval", "describe": "时长", "type": "text", "enable": false }, { "data": "size", "describe": "大小", "type": "text", "enable": true }, { "data": "kbps", "describe": "分辨率", "type": "text", "enable": false }, { "data": "cover", "describe": "封面", "type": "image", "enable": true }, { "data": "songurl", "describe": "歌曲链接", "type": "text", "enable": false }, { "data": "src", "describe": "下载链接", "type": "text", "enable": true } ]; var command1_return_wyydata_Field_default = [ { "data": "songname", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "name", "describe": "歌手", "type": "text", "enable": true }, { "data": "album", "describe": "专辑", "type": "text", "enable": false }, { "data": "pay", "describe": "付费情况", "enable": false, "type": "text" }, { "data": "id", "describe": "歌曲ID", "enable": false, "type": "text" }, { "data": "quality", "describe": "音质", "type": "text", "enable": true }, { "data": "interval", "describe": "时长", "enable": false, "type": "text" }, { "data": "size", "describe": "大小", "type": "text", "enable": true }, { "data": "kbps", "describe": "分辨率", "enable": false, "type": "text" }, { "data": "cover", "describe": "封面", "type": "image", "enable": true }, { "data": "songurl", "describe": "歌曲链接", "type": "text", "enable": false }, { "data": "src", "describe": "下载链接", "type": "text", "enable": true } ]; var command4_return_data_Field_default = [ { "data": "songname", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "name", "describe": "歌手", "type": "text", "enable": true }, { "data": "album", "describe": "专辑", "type": "text", "enable": true }, { "data": "quality", "describe": "音质", "type": "text", "enable": true }, { "data": "interval", "describe": "时长", "type": "text", "enable": false }, { "data": "size", "describe": "大小", "type": "text", "enable": true }, { "data": "kbps", "describe": "分辨率", "type": "text", "enable": false }, { "data": "cover", "describe": "封面", "type": "image", "enable": true }, { "data": "src", "describe": "下载链接", "type": "text", "enable": true }, { "data": "songurl", "describe": "跳转链接", "type": "text", "enable": false } ]; var command5_return_data_Field_default = [ { "data": "name", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "artist", "describe": "歌手", "type": "text", "enable": true }, { "data": "album", "describe": "专辑", "type": "text", "enable": false }, { "data": "source", "describe": "来源平台", "enable": false, "type": "text" }, { "data": "fileSize", "describe": "文件大小", "type": "text", "enable": true }, { "data": "br", "describe": "比特率", "type": "text", "enable": false }, { "data": "coverUrl", "describe": "封面链接", "type": "image", "enable": true }, { "data": "musicUrl", "describe": "下载链接", "type": "text", "enable": true }, { "data": "lyric", "describe": "歌词", "type": "text", "enable": false } ]; var command6_return_data_Field_default = [ { "data": "name", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "id", "describe": "歌曲ID", "type": "text", "enable": true }, { "data": "artist", "describe": "歌手", "type": "text", "enable": true }, { "data": "url", "describe": "下载链接", "type": "text", "enable": true }, { "data": "pic", "describe": "封面链接", "type": "image", "enable": true }, { "data": "lrc", "describe": "歌词", "type": "text", "enable": false } ]; var command7_return_data_Field_default = [ { "type": "text", "data": "type", "describe": "平台名称", "enable": false }, { "data": "link", "describe": "音乐地址", "type": "text", "enable": false }, { "data": "songid", "describe": "歌曲ID", "type": "text", "enable": false }, { "data": "title", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "author", "describe": "歌手", "type": "text", "enable": true }, { "data": "lrc", "describe": "歌词", "type": "text", "enable": false }, { "data": "url", "describe": "下载链接", "type": "text", "enable": true }, { "data": "pic", "describe": "封面链接", "type": "image", "enable": true } ]; var command8_return_QQdata_Field_default = [ { "data": "title", "describe": "歌曲名称", "type": "text", "enable": true }, { "data": "singer", "describe": "歌手", "type": "text", "enable": true }, { "data": "cover", "describe": "封面", "type": "image", "enable": true }, { "data": "link", "describe": "歌曲链接", "type": "text", "enable": false }, { "data": "url", "describe": "下载链接", "type": "text", "enable": true }, { "data": "lyric", "describe": "歌词", "type": "text", "enable": false } ]; var platformMap = { "网易云": "netease", "QQ": "tencent", "酷我": "kuwo", "Tidal": "tidal", "Qobuz": "qobuz", "喜马FM": "ximalaya", "咪咕": "migu", "酷狗": "kugou", "油管": "ytmusic", "Spotify": "spotify" }; var Config = import_koishi.Schema.intersect([ import_koishi.Schema.object({ enableReplySonglist: import_koishi.Schema.boolean().default(false).description("开启后 发送歌单消息的时候 会回复触发指令的消息"), waitTimeout: import_koishi.Schema.natural().role("s").description("允许用户返回选择序号的等待时间").default(45), exitCommand: import_koishi.Schema.string().default("0, 不听了").description("退出选择指令,多个指令间请用逗号分隔开"), // 兼容中文逗号、英文逗号 menuExitCommandTip: import_koishi.Schema.boolean().default(false).description("是否在歌单内容的后面,加上退出选择指令的文字提示") }).description("基础设置"), import_koishi.Schema.object({ imageMode: import_koishi.Schema.boolean().default(true).description("开启后返回图片歌单(需要puppeteer服务),关闭后返回文本歌单(部分指令必须使用puppeteer)"), darkMode: import_koishi.Schema.boolean().default(true).description("是否开启暗黑模式(黑底菜单)") }).description("图片歌单设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.union([ import_koishi.Schema.const("command1").description("command1:星之阁API (需加群申请APIkey) (QQ + 网易云)"), import_koishi.Schema.const("command4").description("command4:星之阁-酷狗API (需加群申请APIkey) (酷狗)"), import_koishi.Schema.const("command5").description("command5:`music.gdstudio.xyz` 网站 (需puppeteer爬取 较慢,但访问性好) (多平台)"), import_koishi.Schema.const("command6").description("command6:`api.injahow.cn`网站 (API 请求快 + 稳定 推荐QQ官方机器人使用) (网易云)"), import_koishi.Schema.const("command7").description("command7:`dev.iw233.cn` 网站 (需puppeteer爬取 较慢) (网易云)"), import_koishi.Schema.const("command8").description("command8:`api.dragonlongzhu.cn` 龙珠API (多平台音乐)") ]).role("radio").default("command6").description("选择使用的后端<br>➣ 推荐度:`api.injahow.cn` ≥ `music.gdstudio.xyz` ≥ `dev.iw233.cn` ≥ `api.dragonlongzhu.cn` > `星之阁API`") }).description("后端选择"), import_koishi.Schema.union([ import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command1").required(), xingzhigeAPIkey: import_koishi.Schema.string().role("secret").description("星之阁的音乐API的请求key<br>(默认值是作者自己的哦,如果失效了请你自己获取一个)<br>请前往 QQ群 905188643 <br>添加QQ好友 3556898686 <br>私聊发送 `/getapikey` 获得你的APIkey以填入此处 ").default("xhsP7Q4MulpzDU6BVwHSKB-j-NfvBxaqiT37hx8djyE="), command1: import_koishi.Schema.string().default("下载音乐").description("星之阁API的指令名称"), command1_wyy_Quality: import_koishi.Schema.number().default(2).description("网易云音乐默认下载音质。默认2,其余自己试 `不建议更改,可能会导致无音源`"), command1_qq_Quality: import_koishi.Schema.number().default(2).description("QQ音乐默认下载音质。音质11为最高 `不建议更改,可能会导致无音源`"), command1_qq_uin: import_koishi.Schema.string().description("QQ音乐搜索:提供skey的账号(当站长提供的cookie失效时必填,届时生效)"), command1_qq_skey: import_koishi.Schema.string().description("QQ音乐搜索:提供开通有绿钻特权的skey可获取vip歌曲(当站长提供的cookie失效时必填,届时生效)为空默认获取站长提供的skey"), command1_return_qqdata_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").default(command1_return_qqdata_Field_default).description("歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](https://api.xingzhige.com/API/QQmusicVIP/?songid=499449053&br=2&uin=2&skey=2&key=)"), command1_return_wyydata_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").default(command1_return_wyydata_Field_default).description("歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](https://api.xingzhige.com/API/NetEase_CloudMusic_new/?name=%E8%94%9A%E8%93%9D%E6%A1%88&n=1&key=)") }).description("星之阁API返回设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command4").required(), xingzhigeAPIkey: import_koishi.Schema.string().role("secret").description("星之阁的音乐API的请求key<br>(默认值是作者自己的哦,如果失效了请你自己获取一个)<br>请前往 QQ群 905188643 <br>添加QQ好友 3556898686 <br>私聊发送 `/getapikey` 获得你的APIkey以填入此处 ").default("xhsP7Q4MulpzDU6BVwHSKB-j-NfvBxaqiT37hx8djyE="), command4: import_koishi.Schema.string().default("酷狗音乐").description("酷狗-星之阁API的指令名称"), command4_kugouQuality: import_koishi.Schema.number().default(1).description("音乐默认下载音质。音质,默认为1"), command4_return_data_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").default(command4_return_data_Field_default).description("歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](https://api.xingzhige.com/API/Kugou_GN_new/?name=蔚蓝档案&pagesize=20&br=2&key=)") }).description("酷狗-星之阁API返回设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command5").required(), command5: import_koishi.Schema.string().default("歌曲搜索").description("`music.gdstudio.xyz`的指令名称"), command5_defaultPlatform: import_koishi.Schema.union([ import_koishi.Schema.const("网易云").description("网易云"), import_koishi.Schema.const("QQ").description("QQ"), import_koishi.Schema.const("酷我").description("酷我"), import_koishi.Schema.const("Tidal").description("Tidal"), import_koishi.Schema.const("Qobuz").description("Qobuz"), import_koishi.Schema.const("喜马FM").description("喜马FM"), import_koishi.Schema.const("咪咕").description("咪咕"), import_koishi.Schema.const("酷狗").description("酷狗"), import_koishi.Schema.const("油管").description("油管"), import_koishi.Schema.const("Spotify").description("Spotify") ]).description("音乐 **默认**使用的平台。").default("网易云"), /* command5_defaultQuality: Schema.union([ Schema.const('128K').description('128K标准 [ 全部音乐源 ]<br>192K较高 [ 网易云 / QQ / Spotify / 咪咕 / 油管 ]'), Schema.const('320K').description('320K高品 [ 全部音乐源 ]'), Schema.const('16bit').description('16bit无损 [ 网易云 / QQ / 酷我 / Tidal / Qobuz / 咪咕 ]'), Schema.const('24bit').description('24bit无损 [ 网易云 / QQ / Tidal / Qobuz ]'), ]).role('radio').description('音乐 **默认**下载音质。').default('320K'), */ command5_searchList: import_koishi.Schema.number().default(20).min(1).max(50).description("歌曲搜索的列表长度。返回的候选项个数。"), command5_page_setTimeout: import_koishi.Schema.number().default(15).min(1).description("等待页面完全加载的等待时间(秒)"), command5_return_data_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").description("歌曲返回信息的字段选择<br>").default(command5_return_data_Field_default) }).description("`music.gdstudio.xyz`返回设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command6"), command6: import_koishi.Schema.string().default("网易点歌").description("`网易点歌`的指令名称<br>输入歌曲ID,返回歌曲"), command6_searchList: import_koishi.Schema.number().default(20).min(1).max(50).description("歌曲搜索的列表长度。返回的候选项个数。"), maxDuration: import_koishi.Schema.natural().description("歌曲最长持续时间,单位为:秒").default(900), command6_useProxy: import_koishi.Schema.boolean().experimental().description("是否使用 Apifox Web Proxy 代理请求(适用于海外用户)").default(false), command6_usedAPI: import_koishi.Schema.union([ import_koishi.Schema.const("api.injahow.cn").description("(稳定)黑胶只能30秒的`api.injahow.cn`后端(适合官方bot)"), import_koishi.Schema.const("meting.jmstrand.cn").description("(推荐)稳定性未知、全部可听的`meting.jmstrand.cn`后端").experimental(), import_koishi.Schema.const("api.qijieya.cn").description("(推荐)稳定性未知、全部可听的`api.qijieya.cn`后端").experimental(), import_koishi.Schema.const("metingapi.nanorocky.top").description("(不推荐 文件很大) 稳定性未知、无损音质、全部可听的`meting.jmstrand.cn`后端").experimental() ]).description("选择 获取音乐直链的后端API").default("api.qijieya.cn"), command6_return_data_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").description("歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](http://music.163.com/api/search/get/web?csrf_token=hlpretag=&hlposttag=&s=蔚蓝档案&type=1&offset=0&total=true&limit=10)").default(command6_return_data_Field_default) }).description("`网易点歌`返回设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command7").required(), command7: import_koishi.Schema.string().default("音乐搜索器").description("`音乐搜索器`的指令名称<br>使用 dev.iw233.cn 提供的网站"), command7_searchList: import_koishi.Schema.number().default(10).min(1).step(1).max(50).description("歌曲搜索的列表长度。返回的候选项个数。<br>为`网易云音乐`的组合"), command7_return_data_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").description("歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](https://dev.iw233.cn/Music1/?name=%E8%94%9A%E8%93%9D%E6%A1%A3%E6%A1%88&type=netease) 需F12 网络标签页 预览响应 `Music1/`").default(command7_return_data_Field_default) }).description("`dev.iw233.cn`返回设置"), import_koishi.Schema.object({ serverSelect: import_koishi.Schema.const("command8").required(), command8: import_koishi.Schema.string().default("龙珠搜索").description("龙珠API的指令名称"), // command8_wyyQuality: Schema.number().default(1).description('QQ音乐默认下载音质。`找不到对应音质,会自动使用标准音质`<br>1(标准音质)/2(极高音质)/3(无损音质)/4(Hi-Res音质)/5(高清环绕声)/6(沉浸环绕声)/7(超清母带)'), command8_searchList: import_koishi.Schema.number().default(20).min(1).max(50).description("歌曲搜索的列表长度。返回的候选项个数。"), command8_return_QQdata_Field: import_koishi.Schema.array(import_koishi.Schema.object({ data: import_koishi.Schema.string().description("返回的字段"), describe: import_koishi.Schema.string().description("对该字段的中文描述"), type: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("文本(text)"), import_koishi.Schema.const("image").description("图片(image)"), import_koishi.Schema.const("audio").description("语音(audio)"), import_koishi.Schema.const("video").description("视频(video)"), import_koishi.Schema.const("file").description("文件(file)") ]).description("字段发送类型"), enable: import_koishi.Schema.boolean().default(true).description("是否启用") })).role("table").default(command8_return_QQdata_Field_default).description("音乐歌曲返回信息的字段选择<br>[➣ 点我查看该API返回内容示例](https://api.dragonlongzhu.cn/api/joox/juhe_music.php?msg=%E8%94%9A%E8%93%9D%E6%A1%A3%E6%A1%88&type=json&br=1&num=20&n=1)") }).description("龙珠API返回设置"), import_koishi.Schema.object({}).description("↑ 请选择后端服务 ↑") ]), import_koishi.Schema.object({ enablemiddleware: import_koishi.Schema.boolean().description("是否自动解析JSON音乐卡片").default(false), middleware: import_koishi.Schema.boolean().description("`enablemiddleware`是否使用前置中间件监听<br>`中间件无法接受到消息可以考虑开启`").default(false), used_id: import_koishi.Schema.number().default(1).min(0).max(10).description("在歌单里默认选择的序号<br>范围`0-10`,无需考虑11-20,会自动根据JSON卡片的平台选择。若音乐平台不匹配 则在搜索项前十个进行选择。") }).description("JSON卡片解析设置"), import_koishi.Schema.object({ isfigure: import_koishi.Schema.boolean().default(false).description("`图片、文本`元素 使用合并转发,其余单独发送<br>`仅支持 onebot 适配器` 其他平台开启 无效").experimental(), isuppercase: import_koishi.Schema.boolean().default(false).description("将链接域名进行大写置换,仅适用于qq官方平台").experimental(), data_Field_Mode: import_koishi.Schema.union([ import_koishi.Schema.const("text").description("富媒体置底:文字 > 图片 > 语音 ≥ 视频 ≥ 文件 (默认)"), import_koishi.Schema.const("image").description("仅图片置顶的 富媒体置底:图片 > 文字 ≥ 语音 ≥ 视频 ≥ 文件 (仅官方机器人考虑使用)"), import_koishi.Schema.const("raw").description("严格按照 `command_return_data_Field` 表格的顺序 (严格按照配置项表格的上下顺序)") ]).role("radio").default("text").description("对 `command*_return_data_Field`配置项 排序的控制<br>优先级越高,顺序越靠前<br>[➣点我查看此配置项 效果预览图](https://i0.hdslb.com/bfs/article/6e8b901f9b9daa57f082bf0cece36102312276085.png)"), renameTempFile: import_koishi.Schema.boolean().default(false).description("是否对`临时音频文件`以`歌曲名称`重命名<br>否则会使用hash值为名称<br>(仅在部分协议端的`h.file`方法下见效)").experimental(), deleteTempTime: import_koishi.Schema.number().default(20).description("对于`file`类型的`Temp`临时文件的删除时间<br>若干`秒`后 删除下载的本地临时文件").experimental() }).description("高级进阶设置"), import_koishi.Schema.object({ loggerinfo: import_koishi.Schema.boolean().default(false).description("日志调试开关") }).description("调试模式") ]); function apply(ctx, config) { const tempDir = import_node_path.default.join(__dirname, "temp"); let isTempDirInitialized = false; const tempFiles = /* @__PURE__ */ new Set(); ctx.on("ready", async () => { ctx.i18n.define("zh-CN", { commands: { [config.command1]: { description: `搜索歌曲`, messages: { "nokeyword": `请输入歌曲相关信息。 ➣示例:/${config.command1} 蔚蓝档案`, "songlisterror": "无法获取歌曲列表,请稍后再试。", "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误" } }, [config.command4]: { description: `搜索酷狗音乐`, messages: { "nokeyword": `请输入歌曲相关信息。 ➣示例:/${config.command4} 蔚蓝档案`, "songlisterror": "获取酷狗音乐数据时发生错误,请稍后再试。", "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误" } }, [config.command5]: { description: `歌曲搜索`, messages: { "nopuppeteer": "没有开启puppeteer服务", "nokeyword": `请输入歌曲相关信息。 ➣示例:/${config.command5} 蔚蓝档案`, "invalidplatform": "`不支持的平台: {0}`;", "songlisterror": "无法获取歌曲列表,请稍后再试。", "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误", "noSearchResults": "没有找到相关的歌曲,请尝试更换关键词或平台。" } }, [config.command6]: { description: `网易云点歌`, messages: { "nopuppeteer": "没有开启puppeteer服务", "nokeyword": `请输入网易云歌曲的 名称 或 ID。 ➣示例:/${config.command6} 蔚蓝档案 ➣示例:/${config.command6} 2608813264`, "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误", "songlisterror": "无法获取歌曲列表,请稍后再试。", "maxsongDuration": "歌曲持续时间超出限制,允许的单曲最大时长为 {0} 秒。" } }, [config.command7]: { description: `音乐搜索器`, messages: { "nopuppeteer": "没有开启puppeteer服务", "nokeyword": `请输入歌曲相关信息。 ➣示例:/${config.command7} 蔚蓝档案`, "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误", "songlisterror": "无法获取歌曲列表,请稍后再试。" } }, [config.command8]: { description: `龙珠音乐`, messages: { "nopuppeteer": "没有开启puppeteer服务", "nokeyword": `请输入歌曲相关信息。 ➣示例:/${config.command8} 蔚蓝档案`, "invalidNumber": "序号输入错误,已退出歌曲选择。", "waitTime": "请在{0}秒内,\n输入歌曲对应的序号:\n➣示例:@机器人 1", "waitTimeout": "输入超时,已取消点歌。", "exitprompt": "已退出歌曲选择。", "noplatform": "获取歌曲失败。", "somerror": "解析歌曲详情时发生错误", "songlisterror": "无法获取歌曲列表,请稍后再试。" } } } }); if (config.enablemiddleware) { ctx.middleware(async (session, next) => { try { const messageElements = await import_koishi.h.parse(session.content); for (const element of messageElements) { if (element.type === "json" && element.attrs && element.attrs.data) { const jsonData = JSON.parse(element.attrs.data); logInfo(JSON.stringify(jsonData, null, 2)); const musicMeta = jsonData?.meta?.music || jsonData?.meta?.news; const tag = musicMeta?.tag; if (musicMeta && tag.includes("音乐")) { const title = musicMeta.title; const desc = musicMeta.desc; logInfo("↡--------------中间件解析--------------↡"); logInfo(tag); logInfo(title); logInfo(desc); logInfo("↟--------------中间件解析--------------↟"); let command = config.serverSelect; let commandName = config[command]; logInfo(commandName); if (!commandName) { commandName = "歌曲搜索"; logger.error(`未找到配置项 ${command} 对应的指令名称,使用默认指令名称 '歌曲搜索'`); } if (command === "command6" && tag === "网易云音乐") { const jumpUrl = musicMeta.jumpUrl; const match = jumpUrl?.match(/id=(\d+)/); if (match && match[1]) { const songId = match[1]; logInfo(`提取到网易云音乐 ID: ${songId}`); await session.execute(`${commandName} ${songId}`); return; } else { logger.error("未能在 jumpUrl 中找到歌曲 ID"); } } else { let usedId = config.used_id; if (tag === "网易云音乐") { if (config.serverSelect === "command1") { usedId += 10; } } logInfo(`使用指令: ${command} ,选择序号:${usedId}`); if (command) { logInfo(`${commandName} -n ${usedId} “${title} ${desc}”`); await session.execute(`${commandName} -n ${usedId} “${title} ${desc}”`); } } } } } } catch (error) { ctx.logger.error(error); await session.send("处理消息时出错。"); } return next(); }, config.middleware); } if (config.serverSelect === "command1") { ctx.command(`${config.command1} <keyword:text>`).option("quality", "-q <value:number> 品质因数").option("number", "-n <number:number> 歌曲序号").action(async ({ session, options }, keyword) => { if (!keyword) return import_koishi.h.text(session.text(".nokeyword")); let qq, netease; try { let res = await searchQQ(ctx.http, keyword); if (typeof res === "string") res = JSON.parse(res); const item = res.request?.data?.body?.item_song; qq = { code: res.code, msg: "", data: Array.isArray(item) ? item.map((v) => ({ songname: v.title.replaceAll("<em>", "").replaceAll("</em>", ""), album: v.album.name, songid: v.id, songurl: `https://y.qq.com/n/ryqq/songDetail/${v.mid}`, name: v.singer.map((v2) => v2.name).join("/") })) : [] }; logInfo(qq); } catch (e) { logger.error("获取QQ音乐数据时发生错误", e); } try { netease = await searchXZG( ctx.http, "NetEase Music", { name: keyword, key: config.xingzhigeAPIkey } ); } catch (e) { logger.error("获取网易云音乐数据时发生错误", e); } const qqData = qq?.data; const neteaseData = netease?.data; if (!qqData?.length && !neteaseData?.length) return import_koishi.h.text(session.text(`.songlisterror`)); const totalQQSongs = qqData?.length ?? 0; const totalNetEaseSongs = neteaseData?.length ?? 0; let serialNumber = options.number; if (serialNumber) { serialNumber = Number(serialNumber); if (Number.isNaN(serialNumber) || serialNumber < 1 || serialNumber > totalQQSongs + totalNetEaseSongs) { return import_koishi.h.text(session.text(`.invalidNumber`)); } } else { const qqListText = qqData?.length ? formatSongList(qqData, "QQ Music", 0, 10) : "<b>QQ Music</b>: 无法获取歌曲列表"; const neteaseListText = neteaseData?.length ? formatSongList(neteaseData, "NetEase Music", qqData?.length ? 0 : 10, 20) : "<b>NetEase Music</b>: 无法获取歌曲列表"; const listText = `${qqListText}<br /><br />${neteaseListText}`; const exitCommands = config.exitCommand.split(/[,,]/).map((cmd) => cmd.trim()); const exitCommandTip = config.menuExitCommandTip ? `退出选择请发[${exitCommands}]中的任意内容<br /><br />` : ""; let quoteId = session.messageId; if (config.imageMode) { const imageBuffer = await generateSongListImage(ctx.puppeteer, listText); const payload = [ import_koishi.h.image(imageBuffer, "image/png"), import_koishi.h.text(`${exitCommandTip.replaceAll("<br />", "\n")}${import_koishi.h.text(session.text(`.waitTime`, [config.waitTimeout]))}`) ]; const msg = await session.send(payload); quoteId = msg.at(-1); } else { const msg = await session.send(`${listText}<br /><br />${exitCommandTip}${import_koishi.h.text(session.text(`.waitTime`, [config.waitTimeout]))}`); quoteId = msg.at(-1); } const input = await session.prompt(config.waitTimeout * 1e3); if (!input) { return quoteId ? import_koishi.h.quote(quoteId) : "" + import_koishi.h.text(session.text(`.waitTimeout`)); } if (exitCommands.includes(input)) { return import_koishi.h.text(session.text(`.exitprompt`)); } serialNumber = +input; if (Number.isNaN(serialNumber) || serialNumber < 1 || serialNumber > totalQQSongs + totalNetEaseSongs) { return import_koishi.h.text(session.text(`.songlisterror`)); } } let platform, songid, br, uin, skey; let selected; if (serialNumber <= totalQQSongs) { selected = qqData[serialNumber - 1]; platform = "QQ Music"; songid = selected.songid; br = config.command1_qq_Quality; uin = config.command1_qq_uin; skey = config.command1_qq_skey; } else { selected = neteaseData[serialNumber - totalQQSongs - 1]; platform = "NetEase Music"; songid = selected.id; br = config.command1_wyy_Quality; uin = "onlyqq"; skey = "onlyqq"; } if (options.quality) { br = options.quality; } if (!platform) return import_koishi.h.text(session.text(`.noplatform`)); const song = await searchXZG(ctx.http, platform, { songid, br, uin, skey, key: config.xingzhigeAPIkey }); if (song.code === 0) { const data = song.data; try { let songDetails; if (serialNumber <= totalQQSongs) { songDetails = generateResponse(session, data, config.command1_return_qqdata_Field); } else { songDetails = generateResponse(session, data, config.command1_return_wyydata_Field); } logInfo(songDetails); return songDetails; } catch (e) { logger.error(e); return import_koishi.h.text(session.text(`.somerror`)); } } else { logger.error(`获取歌曲失败:${JSON.stringify(song)}`); return "获取歌曲失败:" + song.msg; } }); } if (config.serverSelect === "command4") { ctx.command(`${config.command4} <keyword:text>`).option("quality", "-q <value:number> 音质因数").option("number", "-n <number:number> 歌曲序号").action(async ({ session, options }, keyword) => { if (!keyword) return import_koishi.h.text(session.text(`.nokeyword`)); let kugou; try { kugou = await searchKugou(ctx.http, keyword, options.quality || config.command4_kugouQuality); if (kugou.code !== 200) { logger.error(kugou); return import_koishi.h.text(`获取酷狗音乐数据时发生错误`); } } catch (e) { logger.error("获取酷狗音乐数据时发生错误", e); return import_koishi.h.text(session.text(`.songlisterror`)); } const kugouData = kugou?.data; if (!kugouData?.length) return import_koishi.h.text(session.text(`.songlisterror`)); const totalKugouSongs = kugouData.length; let serialNumber = options.number; if (serialNumber) { serialNumber = Number(serialNumber); if (Number.isNaN(serialNumber) || serialNumber < 1 || serialNumber > totalKugouSongs) { return import_koishi.h.text(session.text(`.invalidNumber`)); } } else { const kugouListText = formatSongList(kugouData, "酷狗音乐", 0, 20); const exitCommands = config.exitCommand.split(/[,,]/).map((cmd) => cmd.trim()); const exitCommandTip = config.menuExitCommandTip ? `退出选择请发[${exitCommands}]中的任意内容<br /><br />` : ""; let quoteId = session.messageId; if (config.imageMode) { const imageBuffer = await generateSongListImage(ctx.puppeteer, kugouListText); const payload = [ import_koishi.h.image(imageBuffer, "image/png"), import_koishi.h.text(`${exitCommandTip.replaceAll("<br />", "\n")}${import_koishi.h.text(session.text(`.waitTime`, [config.waitTimeout]))}`) ]; const msg = await session.send(payload); quoteId = msg.at(-1); } else { const msg = await session.send(`${kugouListText}<br /><br />${exitCommandTip}${import_koishi.h.text(session.text(`.waitTime`, [config.waitTimeout]))}`); quoteId = msg.at(-1); } const input = await session.prompt(config.waitTimeout * 1e3); if (!input) { return `${quoteId ? import_koishi.h.quote(quoteId) : ""}输入超时,已取消点歌。`; } if (exitCommands.includes(input)) { return import_koishi.h.text(session.text(`.exitprompt`)); } serialNumber = +input; if (Number.isNaN(serialNumber) || serialNumber < 1 || serialNumber > totalKugouSongs) { return import_koishi.h.text(session.text(`.invalidNumber`)); } } const br = options.quality || config.command4_kugouQuality; const song = await searchKugouSong(ctx.http, keyword, br, serialNumber); if (song.code === 0) { const data = song.data; try { logInfo(song); logInfo(data); const songDetails = generateResponse(session, data, config.command4_return_data_Field); logInfo(songDetails); return songDetails; } catch (e) { logger.error(e); return import_koishi.h.text(session.text(`.somerror`)); } } else { logger.error(`获取歌曲失败:${JSON.stringify(song)}`); return "获取歌曲失败:" + song.msg; } }); } if (config.serverSelect === "command5") { ctx.command(`${config.command5} <keyword:text>`).option("platform", "-p <platform:string> 平台名称").option("number", "-n <number:number> 歌曲序号").example("歌曲搜索 -p QQ -n 1 蔚蓝档案").action(async ({ session, options }, keyword) => { if (!ctx.puppeteer) { await session.send(import_koishi.h.text(session.text(`.nopuppeteer`))); return; } if (!keyword) return import_koishi.h.text(session.text(`.nokeyword`)); const page = await ctx.puppeteer.page(); let searchResults = []; let songDetails = { musicUrl: void 0, coverUrl: void 0, lyric: void 0, musicSize: void 0, musicBr: void 0 }; const exitCommands = config.exitCommand.split(/[,,]/).map((cmd) => cmd.trim()); const handleApiResponse = /* @__PURE__ */ __name((text, type) => { try { const match = text.match(/^jQuery\w+\((.*)\)$/); let jsonData; if (match) { jsonData = JSON.parse(match[1]); } else { jsonData = JSON.parse(text); } if (!jsonData) { ctx.logger.warn(`无法解析 ${type} API 响应: 没有 JSON 数据`); return null; } return jsonData; } catch (error) { ctx.logger.error(`解析 ${type} API 响应失败:`, error, text); return null; } }, "handleApiResponse"); const exitCommandTip = config.menuExitCommandTip ? `退出选择请发[${exitCommands}]中的任意内容 ` : ""; const promptText = `${exitCommandTip}${import_koishi.h.text(session.text(`.waitTime`, [config.waitTimeout]))}`; const waitTimeout = session.text(`.waitTimeout`); const exitprompt = session.text(`.exitprompt`); const invalidNumber = session.text(`.invalidNumber`); let popupError; async function checkAndHandlePopup(page2) { const alert = await page2.$(".layui-layer.layui-layer-msg.layui-layer-hui, .layui-layer-dialog.layui-layer-msg"); if (alert) { const alertText = await page2.evaluate((alertElement) => { const alertContent = alertElement.querySelector(".layui-layer-content"); return alertContent ? alertContent.innerText : null; }, alert); if (alertText) { if (alertText.includes("使用国内节点")) { logInfo("检测到国内节点提示弹窗"); await page2.evaluate((alertElement) => { if (alertElement) alertElement.remove(); }, alert); logInfo("已删除国内节点提示弹窗"); await new Promise((resolve) => ctx.setTimeout(resolve, 1e3)); logInfo("等待1秒后再次检查弹窗"); const secondAlert = await page2.$(".layui-layer.layui-layer-msg.layui-layer-hui, .layui-layer-dialog.layui-layer-msg"); if (secondAlert) { logInfo("开始检测到第二个弹窗"); const secondAlertText = await page2.evaluate((alertElement) => { const alertContent = alertElement.querySelector(".layui-layer-content"); return alertContent ? alertContent.innerText : null; }, secondAlert); if (secondAlertText) { await page2.close(); logInfo(`${secondAlertText}`); return `${secondAlertText}`; } } return null; } if (alertText.includes("已达今日上限")) { await page2.close(); logInfo("该平台请求已达今日上限。"); return `该平台请求已达今日上限。`; } if (alertText.includes("没有找到相关歌曲")) { await page2.close(); logInfo("没有找到相关歌曲,请切换其它音乐源。"); return `没有找到相关歌曲,请切换其它音乐源。`; } if (alertText.includes("播放失败") || alertText.includes("已停止")) { await page2.close(); logInfo("获取失败。没有此歌曲的下载链接。"); return `获取失败。没有此歌曲的下载链接。`; } } } return null; } __name(checkAndHandlePopup, "checkAndHandlePopup"); page.on("response", async (response) => { const url2 = response.url(); if (url2.includes("api.php?callback=jQuery")) { logInfo(url2); try { const text = await response.text(); let jsonData = handleApiResponse(text, "jQuery"); if (!jsonData) { return; } if (Array.isArray(jsonData)) { if (searchResults.length === 0 && jsonData.length > 0 && jsonData[0] && jsonData[0].hasOwnProperty("artist")) { searchResults = jsonData; const extractedSearchResults = []; for (const item of searchResults) { if (item && item.name && item.artist) { extractedSearchResults.push({ songname: item.name, name: item.artist.join("/") }); } } if (!options.number) { const listText = formatSongList(extractedSearchResults, options.platform || config.command5_defaultPlatform, 0, config.command5_searchList); const screenshotPage = await ctx.puppeteer.browser.newPage(); try { const screenshot = await generateSongListImage(ctx.puppeteer, listText); await session.send([ import_koishi.h.image(screenshot, "image/png"), import_koishi.h.text(promptText) ]); } finally { await screenshotPage.close(); } const input = await session.prompt(config.waitTimeout * 1e3); if (!input) { await session.send(import_koishi.h.text(waitTimeout)); await page.close(); return; } if (exitCommands.includes(input)) { await session.send(import_koishi.h.text(exitprompt)); await page.close(); return; } options.number = parseInt(input, 10); } if (isNaN(options.number) || options.number < 1 || options.number > config.command5_searchList || options.number > searchResults.length) { await session.send(import_koishi.h.text(invalidNumber)); await page.close(); return; } const selectedIndex = options.number - 1; const songElement = await page.$(`.list-item[data-no="${selectedIndex}"] .list-num`); if (!songElement) { await session.send("未找到歌曲元素,请检查页面结构。"); await page.close(); return; } await page.evaluate((element) => { const dblclickEvent = new MouseEvent("dblclick", { bubbles: true, cancelable: true, view: window }); element.dispatchEvent(dblclickEvent); }, songElement); logInfo(`已双击歌曲序号: ${options.number}`); po