UNPKG

node-red-contrib-mpd2

Version:

Node-RED nodes to interact with Music Player Daemon

278 lines (253 loc) 13.2 kB
'use strict'; const mpd = require('mpd2'); const { cmd } = mpd; module.exports = function(RED) { function MPDNode(config) { RED.nodes.createNode(this, config); const node = this; this.server = RED.nodes.getNode(config.server); node.command = config.command; node.client = null; node.connected = false; node.connecting = false; // node.status({fill:"red", shape:"ring", text:"未连接"}); // 连接到MPD服务器 function connect() { if (node.connecting || node.connected) return; node.connecting = true; node.status({fill:"yellow", shape:"ring", text:"Connecting..."}); const connectionConfig = { host: node.server.host, port: node.server.port }; if (node.password) { connectionConfig.password = node.password; } mpd.connect(connectionConfig).then(client => { node.client = client; node.connected = true; node.connecting = false; node.status({fill:"green", shape:"dot", text:"Connected"}); // 监听系统事件 client.on('system', (subsystem) => { // 只有当subsystem为'player'时才查询当前播放器状态 if (subsystem === 'player') { node.client.sendCommand('status') .then(response => { let status; try { status = mpd.parseObject(response); } catch (e) { status = response; } // 如果播放器状态为播放中,获取当前播放的音乐信息 if (status.state === 'play') { // 调用currentsong命令获取当前播放的音乐信息 return node.client.sendCommand('currentsong') .then(songResponse => { let songInfo; try { songInfo = mpd.parseObject(songResponse); } catch (e) { songInfo = songResponse; } // 发送包含系统事件、播放器状态和当前歌曲信息的消息 node.send([null, { event: subsystem, result: status, currentsong: songInfo }]); }) .catch(err => { // 如果获取当前歌曲信息失败,仍然发送状态信息 node.error(`Error getting current song information: ${err.message}`, {error: err}); node.send([null, { event: subsystem, result: status, currentsong_error: err.message }]); }); } else { // 如果不是播放状态,只发送状态信息 node.send([null, { event: subsystem, result: status }]); } }) .catch(err => { // 如果状态查询失败,仍然发送系统事件 node.error(`Get status error: ${err.message}`, {error: err}); node.send([null, { event: subsystem, error: err.message }]); }); } else { // 对于非player事件,直接发送系统事件信息,不查询状态 node.send([null, { event: subsystem }]); } }); // 监听关闭事件 client.on('close', () => { node.connected = false; node.status({fill:"red", shape:"ring", text:"Connection closed"}); }); // 监听错误事件 client.on('error', (err) => { node.error(`MPD error: ${err.message}`, {error: err}); node.status({fill:"red", shape:"dot", text:"ERROR: " + err.message}); }); }).catch(err => { node.connecting = false; node.error(`MPD connection error: ${err.message}`, {error: err}); node.status({fill:"red", shape:"dot", text:"connection failed"}); // 尝试重新连接 setTimeout(connect, 5000); }); } // 处理输入消息 node.on('input', function(msg) { // 如果未连接,尝试连接 if (!node.connected) { connect(); return; } let command = ''; let args = []; // 处理不同类型的输入 if (typeof msg.payload === 'string') { // 如果是字符串,直接作为命令 command = msg.payload; // 处理以add开头+空格+URL链接组成的字符串命令 if (command.startsWith('add ')) { const parts = command.split(' '); if (parts.length >= 2) { const url = parts.slice(1).join(' '); // 获取URL部分 if (url.startsWith('http://') || url.startsWith('https://')) { // 对远程链接进行URL编码,但保留协议部分 const protocol = url.startsWith('https://') ? 'https://' : 'http://'; const urlWithoutProtocol = url.substring(protocol.length); // 对于已经包含百分号编码的部分不进行编码 const encodedUrl = protocol + urlWithoutProtocol.split('').map(char => { if (char === '%') { return char; } return encodeURI(char); }).join(''); command = 'add ' + encodedUrl; } else { // 如果不是URL,检查参数是否已经被引号包围 if (!url.startsWith('"') && !url.endsWith('"') && !url.startsWith('\'') && !url.endsWith('\'')) { command = 'add "' + url + '"'; } } } } } else if (typeof msg.payload === 'object') { // 如果是对象,提取命令和参数 if (msg.payload.command.startsWith('add') && args.length > 0) { command = msg.payload.command; if (msg.payload.args) { args = Array.isArray(msg.payload.args) ? msg.payload.args : [msg.payload.args]; if (typeof args[0] === 'string' && (args[0].startsWith('http://') || args[0].startsWith('https://'))) { // 对远程链接进行URL编码,但保留协议部分(http://或https://) const protocol = args[0].startsWith('https://') ? 'https://' : 'http://'; const urlWithoutProtocol = args[0].substring(protocol.length); // 对于已经包含百分号编码的部分不进行编码 args[0] = protocol + urlWithoutProtocol.split('').map(char => { if (char === '%') { return char; } return encodeURI(char); }).join(''); } } } } // 如果没有指定命令,使用默认命令 if (!command && node.command) { command = node.command; } // 如果有命令,发送到MPD服务器 if (command) { let mpdCommand = args.length > 0 ? cmd(command, args) : command; node.client.sendCommand(mpdCommand) .then(response => { // 尝试根据命令类型选择不同的解析方法 try { let result; // 'status' 或 'stats' 使用 parseObject 解析 if (command === 'status' || command === 'stats' || command === 'currentsong') { result = mpd.parseObject(response); } // 'listplaylists', 'list' 或以 'find'/'search' 开头的命令使用 parseList 解析 else if (command === 'listplaylists' || command === 'list' || command === 'listall' || command.startsWith('find') || command.startsWith('search')) { result = mpd.parseList(response); } // 'playlistinfo', 'playlistid', 'listplaylistinfo' 使用 parseNestedList 解析 else if (command === 'playlistinfo' || command === 'playlistid' || command === 'listplaylistinfo') { result = mpd.parseNestedList(response); } // 其他命令使用 autoparseValues 解析 else { result = mpd.autoparseValues(response); } msg.result = result; } catch (e) { // 如果解析失败,返回原始响应 msg.result = response; } if ( msg.result ) { node.send([msg, null]); }; }) .catch(err => { //node.error(`MPD命令错误: ${err.message}`, {error: err}); msg.error = err; node.send([msg, null]); }); } }); // 节点关闭时断开连接 node.on('close', function(done) { if (node.client) { node.client.disconnect().then(() => { node.connected = false; node.client = null; done(); }).catch(err => { node.error(`Disconnect error: ${err.message}`, {error: err}); node.client = null; done(); }); } else { done(); } }); // 尝试初始连接 //if (node.host && node.port) { // connect(); //} // Try to connect on startup if (this.server) { connect(); } else { node.error("MPD server configuration missing"); node.status({fill: "red", shape: "dot", text: "MPD server configuration missing"}); } } // Configuration Node for MPD Server function MPDServerNode(config) { RED.nodes.createNode(this, config); this.host = config.host || 'localhost'; this.port = config.port || 6600; this.password = config.password || ''; } RED.nodes.registerType("mpd2", MPDNode); RED.nodes.registerType("mpd2-server", MPDServerNode); };