roonpipe 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get cacheImage(){return f},get cacheImages(){return d},get clearOldCache(){return s},get getImageCachePath(){return c},get isImageCached(){return o}};for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,r).get});let n=/*#__PURE__*/u(require("node:fs")),a=/*#__PURE__*/u(require("node:os")),i=/*#__PURE__*/u(require("node:path"));function u(e){return e&&e.__esModule?e:{default:e}}let l=i.default.join(a.default.homedir(),".cache","roonpipe","images");function c(e){return i.default.join(l,`${e}.jpg`)}function o(e){return n.default.existsSync(c(e))}async function f(e,t){if(!t)return null;let r=c(t);return o(t)?r:new Promise(a=>{e.get_image(t,{scale:"fit",width:300,height:300,format:"image/jpeg"},(e,t,i)=>{if(e||!i)return void a(null);try{n.default.writeFileSync(r,i),a(r)}catch{a(null)}})})}async function d(e,t){let r=new Map,n=[...new Set(t.filter(Boolean))];return await Promise.all(n.map(async t=>{let n=await f(e,t);r.set(t,n)})),r}function s(e=30){try{let t=n.default.readdirSync(l),r=24*e*36e5,a=Date.now();for(let e of t){let t=i.default.join(l,e),u=n.default.statSync(t);a-u.mtimeMs>r&&n.default.unlinkSync(t)}}catch{}}n.default.existsSync(l)||n.default.mkdirSync(l,{recursive:!0});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get cacheImage(){return f},get cacheImages(){return d},get clearOldCache(){return s},get getImageCachePath(){return c},get isImageCached(){return o}};for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,r).get});let n=/*#__PURE__*/u(require("node:fs")),a=/*#__PURE__*/u(require("node:os")),i=/*#__PURE__*/u(require("node:path"));function u(e){return e&&e.__esModule?e:{default:e}}let l=i.default.join(a.default.homedir(),".cache","roonpipe","images");function c(e){return i.default.join(l,`${e}.jpg`)}function o(e){return n.default.existsSync(c(e))}async function f(e,t){if(!t)return null;let r=c(t);return o(t)?r:new Promise(a=>{e.get_image(t,{scale:"fit",width:300,height:300,format:"image/jpeg"},(e,t,i)=>{if(e||!i)return void a(null);try{n.default.mkdirSync(l,{recursive:!0}),n.default.writeFileSync(r,i),a(r)}catch{a(null)}})})}async function d(e,t){let r=new Map,n=[...new Set(t.filter(Boolean))];return await Promise.all(n.map(async t=>{let n=await f(e,t);r.set(t,n)})),r}function s(e=30){try{let t=n.default.readdirSync(l),r=24*e*36e5,a=Date.now();for(let e of t){let t=i.default.join(l,e),u=n.default.statSync(t);a-u.mtimeMs>r&&n.default.unlinkSync(t)}}catch{}}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});let e=require("node:child_process"),o=require("node:fs"),r=require("node:path"),n=require("./cli"),s=require("./gnome-search-provider"),i=require("./mpris"),t=require("./notification"),a=require("./roon"),c=require("./socket"),l=process.argv.includes("--install-gnome"),u=process.argv.includes("--cli");function d(){return(process.env.XDG_CURRENT_DESKTOP?.includes("GNOME")??!1)||(process.env.DESKTOP_SESSION?.includes("gnome")??!1)}if(l){let o=(0,r.join)(__dirname,"../scripts/install-gnome-search-provider.sh");process.getuid&&0!==process.getuid()?(console.log("To install GNOME Search Provider, you need to run this command as root or with sudo:"),console.log(`sudo bash "${o}"`),process.exit(1)):(console.log("Installing GNOME Search Provider..."),(0,e.execSync)(`bash "${o}"`,{stdio:"inherit"}),process.exit(0))}u?(0,n.startCLI)():(process.getuid&&0===process.getuid()&&(console.error("❌ Running as root. Please run as a regular user for the daemon."),process.exit(1)),(0,c.isInstanceRunning)().then(e=>{e&&(console.error("❌ Another instance of RoonPipe is already running."),console.error(" Stop the existing instance first, or use --cli to connect to it."),process.exit(1)),console.log("Starting RoonPipe Daemon"),d()&&((0,o.existsSync)("/usr/share/gnome-shell/search-providers/com.bluemancz.RoonPipe.SearchProvider.ini")||(console.warn("⚠️ GNOME Search Provider not installed."),console.warn(" Run 'roonpipe --install-gnome' to enable searching from GNOME overview."))),(0,i.initMpris)(()=>(0,a.getCore)()?.services.RoonApiTransport,a.getZone),(0,a.initRoon)({onCorePaired:e=>{(0,c.startSocketServer)({search:a.searchRoon,play:a.playItem}),d()&&(0,s.initGnomeSearchProvider)(a.searchRoon,a.playItem)},onCoreUnpaired:e=>{(0,i.updateMprisMetadata)(null,null)},onZoneChanged:(e,o)=>{(0,i.updateMprisMetadata)(e,o),(0,t.showTrackNotification)(e,o)},onSeekChanged:e=>{(0,i.updateMprisSeek)(e)}})}));
2
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});let e=require("node:child_process"),o=require("node:fs"),r=require("node:path"),n=require("./cli"),s=require("./gnome-search-provider"),i=require("./image-cache"),t=require("./mpris"),a=require("./notification"),c=require("./roon"),l=require("./socket"),u=process.argv.includes("--install-gnome"),d=process.argv.includes("--cli");function p(){return(process.env.XDG_CURRENT_DESKTOP?.includes("GNOME")??!1)||(process.env.DESKTOP_SESSION?.includes("gnome")??!1)}if(u){let o=(0,r.join)(__dirname,"../scripts/install-gnome-search-provider.sh");process.getuid&&0!==process.getuid()?(console.log("To install GNOME Search Provider, you need to run this command as root or with sudo:"),console.log(`sudo bash "${o}"`),process.exit(1)):(console.log("Installing GNOME Search Provider..."),(0,e.execSync)(`bash "${o}"`,{stdio:"inherit"}),process.exit(0))}d?(0,n.startCLI)():(process.getuid&&0===process.getuid()&&(console.error("❌ Running as root. Please run as a regular user for the daemon."),process.exit(1)),(0,l.isInstanceRunning)().then(e=>{e&&(console.error("❌ Another instance of RoonPipe is already running."),console.error(" Stop the existing instance first, or use --cli to connect to it."),process.exit(1)),console.log("Starting RoonPipe Daemon"),(0,i.clearOldCache)(),p()&&((0,o.existsSync)("/usr/share/gnome-shell/search-providers/com.bluemancz.RoonPipe.SearchProvider.ini")||(console.warn("⚠️ GNOME Search Provider not installed."),console.warn(" Run 'roonpipe --install-gnome' to enable searching from GNOME overview."))),(0,t.initMpris)(()=>(0,c.getCore)()?.services.RoonApiTransport,c.getZone),(0,c.initRoon)({onCorePaired:e=>{(0,l.startSocketServer)({search:c.searchRoon,play:c.playItem}),p()&&(0,s.initGnomeSearchProvider)(c.searchRoon,c.playItem)},onCoreUnpaired:e=>{(0,t.updateMprisMetadata)(null,null)},onZoneChanged:(e,o)=>{(0,t.updateMprisMetadata)(e,o),(0,a.showTrackNotification)(e,o)},onSeekChanged:e=>{(0,t.updateMprisSeek)(e)}})}));
package/dist/mpris.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=exports,a={get initMpris(){return u},get updateMprisMetadata(){return p},get updateMprisSeek(){return c}};for(var o in a)Object.defineProperty(t,o,{enumerable:!0,get:Object.getOwnPropertyDescriptor(a,o).get});let s=(e=require("mpris-service"))&&e.__esModule?e:{default:e},i=require("./image-cache"),l=require("./roon"),n=null,r={loop_one:"Track",loop:"Playlist",disabled:"None",next:"None",Track:"loop_one",Playlist:"loop",None:"disabled"};function u(e,t){function a(a){let o=e(),s=t();o&&s&&a(o,s)}n=(0,s.default)({name:"roon",identity:"Roon",supportedUriSchemes:["file"],supportedMimeTypes:["audio/mpeg","application/ogg"],supportedInterfaces:["player"]}),["play","pause","stop","playpause","next","previous","seek","position","volume","loopStatus","shuffle","open"].forEach(e=>{n.on(e,t=>{switch(e){case"seek":a((e,a)=>e.seek(a,"relative",t/1e6));break;case"position":a((e,a)=>e.seek(a,"absolute",t.position/1e6));break;case"volume":a((e,a)=>{if(!a.outputs?.[0]?.volume)return;let o=a.outputs[0],s=o.volume,i=s.min+t*(s.max-s.min);e.change_volume(o,"absolute",Math.round(i))});break;case"loopStatus":a((e,a)=>e.change_settings(a,{loop:r[t]||"disabled"}));break;case"shuffle":a((e,a)=>e.change_settings(a,{shuffle:t}));break;case"open":console.log("MPRIS command: open",t.uri);break;default:a((t,a)=>t.control(a,e))}})}),n.getPosition=()=>{let e=t();return e?.now_playing?.seek_position?1e6*e.now_playing.seek_position:0},n.canControl=!0,n.canPlay=!0,n.canPause=!0,n.canGoNext=!0,n.canGoPrevious=!0,n.canSeek=!0,n.playbackStatus="Stopped",console.log("MPRIS player initialized")}async function p(e,t){if(!n)return;if(!e||!e.now_playing){n.metadata={},n.playbackStatus="Stopped";return}let a=e.now_playing,o=(0,l.parseNowPlaying)(a),s=e.is_play_allowed||e.is_pause_allowed;n.canPlay=s,n.canPause=s,n.canGoNext=e.is_next_allowed,n.canGoPrevious=e.is_previous_allowed,n.playbackStatus="playing"===e.state?"Playing":"Paused",n.loopStatus=r[e.settings.loop]||"None",n.shuffle=e.settings.shuffle,n.canSeek=e.is_seek_allowed,function(e){if(!n)return;if(!e||!e.outputs||0===e.outputs.length){n.volume=0;return}let t=e.outputs[0];if(!t.volume){n.volume=0;return}let a=t.volume;n.volume=(a.value-a.min)/(a.max-a.min)}(e);let u=null;if(a.image_key&&t?.services?.RoonApiImage)try{u=await (0,i.cacheImage)(t.services.RoonApiImage,a.image_key)}catch(e){u=null}n.metadata={"mpris:trackid":n.objectPath(`track/${a.image_key||"0"}`),"xesam:title":o.title,"xesam:artist":o.artists,"xesam:album":o.album,"mpris:length":1e6*(a.length||0),...u&&{"mpris:artUrl":`file://${u}`}}}function c(e){n&&n.seeked(e)}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=exports,a={get initMpris(){return u},get updateMprisMetadata(){return p},get updateMprisSeek(){return c}};for(var o in a)Object.defineProperty(t,o,{enumerable:!0,get:Object.getOwnPropertyDescriptor(a,o).get});let s=(e=require("mpris-service"))&&e.__esModule?e:{default:e},i=require("./image-cache"),n=require("./roon"),l=null,r={loop_one:"Track",loop:"Playlist",disabled:"None",next:"None",Track:"loop_one",Playlist:"loop",None:"disabled"};function u(e,t){function a(a){let o=e(),s=t();o&&s&&a(o,s)}l=(0,s.default)({name:"roon",identity:"Roon",supportedUriSchemes:["file"],supportedMimeTypes:["audio/mpeg","application/ogg"],supportedInterfaces:["player"]}),["play","pause","stop","playpause","next","previous","seek","position","volume","loopStatus","shuffle","open"].forEach(e=>{l.on(e,t=>{switch(e){case"seek":a((e,a)=>e.seek(a,"relative",t/1e6));break;case"position":a((e,a)=>e.seek(a,"absolute",t.position/1e6));break;case"volume":a((e,a)=>{if(!a.outputs?.[0]?.volume)return;let o=a.outputs[0],s=o.volume,i=s.min+t*(s.max-s.min);e.change_volume(o,"absolute",Math.round(i))});break;case"loopStatus":a((e,a)=>e.change_settings(a,{loop:r[t]||"disabled"}));break;case"shuffle":a((e,a)=>e.change_settings(a,{shuffle:t}));break;case"open":console.log("MPRIS command: open",t.uri);break;default:a((t,a)=>t.control(a,e))}})}),l.getPosition=()=>{let e=t();return e?.now_playing?.seek_position?1e6*e.now_playing.seek_position:0},l.canControl=!0,l.canPlay=!0,l.canPause=!0,l.canGoNext=!0,l.canGoPrevious=!0,l.canSeek=!0,l.playbackStatus="Stopped",console.log("MPRIS player initialized")}async function p(e,t){if(!l)return;if(!e||!e.now_playing){l.metadata={},l.playbackStatus="Stopped";return}let a=e.now_playing,o=(0,n.parseNowPlaying)(a),s=e.is_play_allowed||e.is_pause_allowed;l.canPlay=s,l.canPause=s,l.canGoNext=e.is_next_allowed,l.canGoPrevious=e.is_previous_allowed,l.playbackStatus="playing"===e.state?"Playing":"Paused",l.loopStatus=r[e.settings.loop]||"None",l.shuffle=e.settings.shuffle,l.canSeek=e.is_seek_allowed,function(e){if(!l)return;if(!e||!e.outputs||0===e.outputs.length){l.volume=0;return}let t=e.outputs[0];if(!t.volume){l.volume=0;return}let a=t.volume;l.volume=(a.value-a.min)/(a.max-a.min)}(e);let u=a.image_key||"0",p={"mpris:trackid":l.objectPath(`track/${u}`),"xesam:title":o.title,"xesam:artist":o.artists,"xesam:album":o.album,"mpris:length":1e6*(a.length||0)};if(l.metadata=p,a.image_key&&t?.services?.RoonApiImage)try{let e=await (0,i.cacheImage)(t.services.RoonApiImage,a.image_key);e&&(l.metadata={...p,"mpris:artUrl":`file://${e}`})}catch(e){}}function c(e){l&&l.seeked(e)}
package/dist/roon.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get getCore(){return g},get getZone(){return y},get initRoon(){return d},get parseNowPlaying(){return _},get playItem(){return p},get searchRoon(){return m}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let n=/*#__PURE__*/s(require("node-roon-api")),o=/*#__PURE__*/s(require("node-roon-api-browse")),r=/*#__PURE__*/s(require("node-roon-api-image")),l=/*#__PURE__*/s(require("node-roon-api-transport")),a=require("./image-cache");function s(e){return e&&e.__esModule?e:{default:e}}let u=null,c=null,_=e=>{let t=e.three_line?.line1||"Unknown Track";return{title:t,artists:e.three_line?.line2?[e.three_line.line2.split(" / ").map(e=>e.trim())[0]]:["Unknown Artist"],album:e.three_line?.line3||""}};function d(e){let t=new n.default({extension_id:"com.bluemancz.roonpipe",display_name:"RoonPipe",display_version:"1.0.6",publisher:"BlueManCZ",email:"your@email.com",website:"https://github.com/bluemancz/roonpipe",log_level:"none",core_paired:t=>{c=t,t.services.RoonApiTransport.subscribe_zones((i,n)=>{if("Subscribed"===i)u=n.zones.find(e=>"playing"===e.state)||n.zones[0],e.onZoneChanged(u,t);else if("Changed"===i){if(n.zones_changed){let i=n.zones_changed.find(e=>"playing"===e.state);i?u=i:u&&(u=n.zones_changed.find(e=>e.zone_id===u.zone_id)||u),e.onZoneChanged(u,t)}if(n.zones_seek_changed){let t=n.zones_seek_changed.find(e=>e.zone_id===u?.zone_id);t&&u?.now_playing&&(u.now_playing.seek_position=t.seek_position,e.onSeekChanged(1e6*t.seek_position))}}}),e.onCorePaired(t),console.log(`Core paired: ${t.display_name}`)},core_unpaired:t=>{u=null,c=null,e.onCoreUnpaired(t),console.log(`Core unpaired: ${t.display_name}`)}});t.init_services({required_services:[o.default,r.default,l.default]}),t.start_discovery()}function f(e,t){return new Promise((i,n)=>{e(t,(e,t)=>{e?n(e):i(t)})})}async function m(e){if(!c)throw Error("Roon Core not connected");if(!u)throw Error("No active zone");let t=c.services.RoonApiBrowse,i=`search_${Date.now()}`,n=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:u.zone_id,...e}),o=await f(t.browse.bind(t),n({input:e})),r=await f(t.load.bind(t),n({input:e,offset:0,count:o.list.count})),l=[];for(let e of r.items){if(!e.title)continue;let i=n({item_key:e.item_key}),o=await f(t.browse.bind(t),i),r=await f(t.load.bind(t),{...i,offset:0,count:Math.min(o.list.count,5)}),s=r.items?.map(e=>e.image_key).filter(Boolean)||[],u=await (0,a.cacheImages)(c.services.RoonApiImage,s),_=e.title.toLowerCase(),d=_.includes("composer")||_.includes("artist");l.push({category:e,items:r.items||[],cachedImages:u,isArtistCategory:d})}let s=new Map;for(let{items:e,cachedImages:t,isArtistCategory:i}of l)if(i)for(let i of e){let e=t.get(i.image_key);i.title&&e&&s.set(i.title,e)}let _=[];for(let{category:e,items:t,cachedImages:n,isArtistCategory:o}of l){if(o)continue;let r=function(e){let t=e.toLowerCase();return["artist","album","composer","playlist","track","work"].find(e=>t.includes(e))||"track"}(e.title);for(let o=0;o<t.length;o++){var d;let l=t[o],a="action_list"===l.hint&&"Play Artist"===l.title,u=a?"artist":r,c=function(e,t){let i=[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}],n=[{title:"Shuffle"},{title:"Start Radio"}];if("action_list"===t)return"track"===e?i:n;switch(e){case"album":case"track":return i;case"artist":case"composer":return n;case"playlist":return[{title:"Play Now"},{title:"Shuffle"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}];default:return[]}}(u,l.hint),f=a?e.title:null,m=n.get(l.image_key)||f&&s.get(f)||null;if(_.push({title:a?e.title:l.title||`Unknown ${u.charAt(0).toUpperCase()+u.slice(1)}`,subtitle:(d=l.subtitle)?d.replace(/\[\[(\d+)\|([^\]]+)]]/g,"$2").split(", ")[0].trim():"",item_key:l.item_key,image:m,hint:l.hint,sessionKey:i,type:u,category_key:e.item_key,index:o,actions:c}),a)break}}return _}async function p(e,t,i,n,o){if(!c)throw Error("Roon Core not connected");if(!u)throw Error("No active zone");let r=c.services.RoonApiBrowse,l=(e,t={})=>({hierarchy:"search",multi_session_key:e,zone_or_output_id:u.zone_id,...t});console.log(`[DEBUG] playItem: itemKey=${e}, categoryKey=${i}, itemIndex=${n}, actionTitle=${o}`),await f(r.browse.bind(r),l(t,{item_key:i}));let a=await f(r.load.bind(r),l(t,{item_key:i,offset:n,count:1}));if(!a.items?.[0])throw Error("Item not found at index");let s=a.items[0].item_key;async function _(e,t,i=0){if(i>5)return!1;let n=await f(r.browse.bind(r),l(t,{item_key:e})),a=n.list?.multi_session_key||t,s=await f(r.load.bind(r),l(a,{item_key:e,offset:0,count:n.list?.count||50}));if(!s.items?.length)return!1;for(let e of s.items){if(console.log(`[DEBUG] Navigating: title=${e.title}, hint=${e.hint}`),"action"===e.hint&&e.title===o)return console.log(`[DEBUG] Found action! Executing: ${e.title} (${e.item_key})`),await f(r.browse.bind(r),l(a,{item_key:e.item_key})),console.log("[DEBUG] Successfully executed action"),!0;if("action_list"===e.hint||"list"===e.hint&&1===s.items.length){if(await _(e.item_key,a,i+1))return!0;if(1===i&&"action_list"===e.hint)break}}return!1}if(console.log(`[DEBUG] Got fresh item_key: ${s}`),!await _(s,t))throw Error(`Could not find action "${o}" to execute`)}function y(){return u}function g(){return c}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get getCore(){return y},get getZone(){return g},get initRoon(){return d},get parseNowPlaying(){return _},get playItem(){return m},get searchRoon(){return p}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let n=/*#__PURE__*/s(require("node-roon-api")),o=/*#__PURE__*/s(require("node-roon-api-browse")),r=/*#__PURE__*/s(require("node-roon-api-image")),a=/*#__PURE__*/s(require("node-roon-api-transport")),l=require("./image-cache");function s(e){return e&&e.__esModule?e:{default:e}}let c=null,u=null,_=e=>{let t=e.three_line?.line1||"Unknown Track";return{title:t,artists:e.three_line?.line2?[e.three_line.line2.split(" / ").map(e=>e.trim())[0]]:["Unknown Artist"],album:e.three_line?.line3||""}};function d(e){let t=new n.default({extension_id:"com.bluemancz.roonpipe",display_name:"RoonPipe",display_version:"1.0.8",publisher:"BlueManCZ",email:"your@email.com",website:"https://github.com/bluemancz/roonpipe",log_level:"none",core_paired:t=>{u=t,t.services.RoonApiTransport.subscribe_zones((i,n)=>{if("Subscribed"===i)c=n.zones.find(e=>"playing"===e.state)||n.zones[0],e.onZoneChanged(c,t);else if("Changed"===i){if(n.zones_changed){let i=n.zones_changed.find(e=>"playing"===e.state);i?c=i:c&&(c=n.zones_changed.find(e=>e.zone_id===c.zone_id)||c),e.onZoneChanged(c,t)}if(n.zones_seek_changed){let i=n.zones_seek_changed.find(e=>e.zone_id===c?.zone_id);i&&c?.now_playing&&(c.now_playing.seek_position=i.seek_position,e.onSeekChanged(1e6*i.seek_position),n.zones_changed||"playing"===c.state||(c.state="playing",e.onZoneChanged(c,t)))}}}),e.onCorePaired(t),console.log(`Core paired: ${t.display_name}`)},core_unpaired:t=>{c=null,u=null,e.onCoreUnpaired(t),console.log(`Core unpaired: ${t.display_name}`)}});t.init_services({required_services:[o.default,r.default,a.default]}),t.start_discovery()}function f(e,t){return new Promise((i,n)=>{e(t,(e,t)=>{e?n(e):i(t)})})}async function p(e){if(!u)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let t=u.services.RoonApiBrowse,i=`search_${Date.now()}`,n=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:c.zone_id,...e}),o=await f(t.browse.bind(t),n({input:e})),r=await f(t.load.bind(t),n({input:e,offset:0,count:o.list.count})),a=[];for(let e of r.items){if(!e.title)continue;let i=n({item_key:e.item_key}),o=await f(t.browse.bind(t),i),r=await f(t.load.bind(t),{...i,offset:0,count:Math.min(o.list.count,5)}),s=r.items?.map(e=>e.image_key).filter(Boolean)||[],c=await (0,l.cacheImages)(u.services.RoonApiImage,s),_=e.title.toLowerCase(),d=_.includes("composer")||_.includes("artist");a.push({category:e,items:r.items||[],cachedImages:c,isArtistCategory:d})}let s=new Map;for(let{items:e,cachedImages:t,isArtistCategory:i}of a)if(i)for(let i of e){let e=t.get(i.image_key);i.title&&e&&s.set(i.title,e)}let _=[];for(let{category:e,items:t,cachedImages:n,isArtistCategory:o}of a){if(o)continue;let r=function(e){let t=e.toLowerCase();return["artist","album","composer","playlist","track","work"].find(e=>t.includes(e))||"track"}(e.title);for(let o=0;o<t.length;o++){var d;let a=t[o],l="action_list"===a.hint&&"Play Artist"===a.title,c=l?"artist":r,u=function(e,t){let i=[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}],n=[{title:"Shuffle"},{title:"Start Radio"}];if("action_list"===t)return"track"===e?i:n;switch(e){case"album":case"track":return i;case"artist":case"composer":return n;case"playlist":return[{title:"Play Now"},{title:"Shuffle"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}];default:return[]}}(c,a.hint),f=l?e.title:null,p=n.get(a.image_key)||f&&s.get(f)||null;if(_.push({title:l?e.title:a.title||`Unknown ${c.charAt(0).toUpperCase()+c.slice(1)}`,subtitle:(d=a.subtitle)?d.replace(/\[\[(\d+)\|([^\]]+)]]/g,"$2").split(", ")[0].trim():"",item_key:a.item_key,image:p,hint:a.hint,sessionKey:i,type:c,category_key:e.item_key,index:o,actions:u}),l)break}}return _}async function m(e,t,i,n,o){if(!u)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let r=u.services.RoonApiBrowse,a=(e,t={})=>({hierarchy:"search",multi_session_key:e,zone_or_output_id:c.zone_id,...t});console.log(`[DEBUG] playItem: itemKey=${e}, categoryKey=${i}, itemIndex=${n}, actionTitle=${o}`),await f(r.browse.bind(r),a(t,{item_key:i}));let l=await f(r.load.bind(r),a(t,{item_key:i,offset:n,count:1}));if(!l.items?.[0])throw Error("Item not found at index");let s=l.items[0].item_key;async function _(e,t,i=0){if(i>5)return!1;let n=await f(r.browse.bind(r),a(t,{item_key:e})),l=n.list?.multi_session_key||t,s=await f(r.load.bind(r),a(l,{item_key:e,offset:0,count:n.list?.count||50}));if(!s.items?.length)return!1;for(let e of s.items){if(console.log(`[DEBUG] Navigating: title=${e.title}, hint=${e.hint}`),"action"===e.hint&&e.title===o)return console.log(`[DEBUG] Found action! Executing: ${e.title} (${e.item_key})`),await f(r.browse.bind(r),a(l,{item_key:e.item_key})),console.log("[DEBUG] Successfully executed action"),!0;if("action_list"===e.hint||"list"===e.hint&&1===s.items.length){if(await _(e.item_key,l,i+1))return!0;if(1===i&&"action_list"===e.hint)break}}return!1}if(console.log(`[DEBUG] Got fresh item_key: ${s}`),!await _(s,t))throw Error(`Could not find action "${o}" to execute`)}function g(){return c}function y(){return u}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roonpipe",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Linux integration for Roon – MPRIS support, media keys, desktop notifications, and interactive search CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {