roonpipe 1.0.11 → 1.0.13

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";var e,t;Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"initGnomeSearchProvider",{enumerable:!0,get:function(){return S}});let r=(t=require("dbus-next"))&&t.__esModule?t:{default:t},n="com.bluemancz.RoonPipe.SearchProvider",a=new Map,i=new Map,o=null,l=null,s=null,u=(e=async(e,t)=>{let r=i.get(e);if(r)return void t(r);try{if(!l)return void t([]);let r=await l(e),n=[];for(let e of r.slice(0,5)){let t=`roon_${e.item_key}`;a.set(t,e),n.push(t)}i.set(e,n),t(n)}catch(e){console.error("Search failed:",e),t([])}},(...t)=>{o&&clearTimeout(o),o=setTimeout(()=>e(...t),300)});async function c(e){let t=e.join(" ");return t.length<4||!l?[]:new Promise(e=>{u(t,e)})}let{Interface:d}=r.default.interface;class g extends d{constructor(){super("org.gnome.Shell.SearchProvider2")}async GetInitialResultSet(e){return c(e)}async GetSubsearchResultSet(e,t){return c(t)}async GetResultMetas(e){let t=r.default.Variant,n=[];for(let r of e){let e=a.get(r);if(e){let a={id:new t("s",r),name:new t("s",e.title),description:new t("s",e.subtitle)};e.image&&(a.gicon=new t("s",e.image)),n.push(a)}}return n}async ActivateResult(e,t,r){let n=a.get(e);if(n&&s)try{if(n.actions.length>0){let e=n.actions.find(e=>"Play Now"===e.title)||n.actions.find(e=>"Shuffle"===e.title)||n.actions[0];await s(n.item_key,n.sessionKey,n.category_key,n.index,e.title,n.title,n.type,n.image_key),console.log(`Playing: ${n.title}`)}}catch(e){console.error("Failed to play track:",e)}}async LaunchSearch(e,t){console.log("LaunchSearch called - not implemented")}}async function S(e,t){l=e,s=t;try{let e=r.default.sessionBus();await e.requestName(n,0);let t=new g;e.export("/com/bluemancz/RoonPipe/SearchProvider",t),console.log("GNOME Search Provider initialized on",n)}catch(e){console.error("Failed to initialize GNOME Search Provider:",e)}}g.configureMembers({methods:{GetInitialResultSet:{inSignature:"as",outSignature:"as"},GetSubsearchResultSet:{inSignature:"asas",outSignature:"as"},GetResultMetas:{inSignature:"as",outSignature:"aa{sv}"},ActivateResult:{inSignature:"sasu",outSignature:""},LaunchSearch:{inSignature:"asu",outSignature:""}}});
1
+ "use strict";var e,t;Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"initGnomeSearchProvider",{enumerable:!0,get:function(){return f}});let r=(t=require("dbus-next"))&&t.__esModule?t:{default:t},n="com.bluemancz.RoonPipe.SearchProvider",i=new Map,a=new Map,o=null,l=null,s=null,u=(e=async(e,t)=>{let r=a.get(e);if(r)return void t(r);try{if(!l)return void t([]);let r=await l(e),n=[];for(let e of r.slice(0,5)){let t=`roon_${e.item_key}`;i.set(t,e),n.push(t)}a.set(e,n),t(n)}catch(e){console.error("Search failed:",e),t([])}},(...t)=>{o&&clearTimeout(o),o=setTimeout(()=>e(...t),300)});async function c(e){let t=e.join(" ");return t.length<4||!l?[]:new Promise(e=>{u(t,e)})}let{Interface:d}=r.default.interface;class g extends d{constructor(){super("org.gnome.Shell.SearchProvider2")}async GetInitialResultSet(e){return c(e)}async GetSubsearchResultSet(e,t){return c(t)}async GetResultMetas(e){let t=r.default.Variant,n=[];for(let r of e){let e=i.get(r);if(e){let i={id:new t("s",r),name:new t("s",e.title),description:new t("s",e.subtitle)};e.image&&(i.gicon=new t("s",e.image)),n.push(i)}}return n}async ActivateResult(e,t,r){let n=i.get(e);if(n&&s)try{if(n.actions.length>0){let e=n.actions.find(e=>"Play Now"===e.title)||n.actions.find(e=>"Shuffle"===e.title)||n.actions[0];await s(n.item_key,n.sessionKey,n.category_key,n.index,e.title,n.title,n.type,n.image_key),console.log(`Playing: ${n.title}`)}}catch(e){console.error("Failed to play track:",e)}}async LaunchSearch(e,t){console.log("LaunchSearch called - not implemented")}}g.configureMembers({methods:{GetInitialResultSet:{inSignature:"as",outSignature:"as"},GetSubsearchResultSet:{inSignature:"asas",outSignature:"as"},GetResultMetas:{inSignature:"as",outSignature:"aa{sv}"},ActivateResult:{inSignature:"sasu",outSignature:""},LaunchSearch:{inSignature:"asu",outSignature:""}}});let S=!1;async function f(e,t){if(l=e,s=t,!S)try{let e=r.default.sessionBus();await e.requestName(n,0);let t=new g;e.export("/com/bluemancz/RoonPipe/SearchProvider",t),S=!0,console.log("GNOME Search Provider initialized on",n)}catch(e){console.error("Failed to initialize GNOME Search Provider:",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"),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)}
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.on("error",e=>{console.error("MPRIS error:",e)}),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)}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"showTrackNotification",{enumerable:!0,get:function(){return r}});let e=require("node:child_process"),i=require("./image-cache"),n=require("./roon"),t=null,a=null,o=!0;async function r(r,l){if(o){o=!1,r?.now_playing&&(t=r.now_playing.image_key||"",a=r.state);return}if(!r?.now_playing)return;let c=r.now_playing.image_key||"",s="playing"===r.state,g=a,p=c!==t;if(!(s&&"playing"!==g||p&&s)){a=r.state;return}try{let o=(0,n.parseNowPlaying)(r.now_playing),s=null;if(r.now_playing.image_key&&l?.services?.RoonApiImage)try{s=await (0,i.cacheImage)(l.services.RoonApiImage,r.now_playing.image_key)}catch(e){s=null}let g=["--app-name=Roon","--icon=audio-x-generic","--replace-id=1","--hint=int:transient:1",...s?[`--hint=string:image-path:${s}`]:[],"--expire-time=5000",o.title,`${o.artists.join(", ")} • ${o.album}`];console.log(`Showing notification for track: ${o.title}`),(0,e.execSync)(`notify-send ${g.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" ")}`),t=c,a=r.state}catch(e){console.error("Failed to show notification:",e)}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"showTrackNotification",{enumerable:!0,get:function(){return r}});let e=require("node:child_process"),i=require("./image-cache"),n=require("./roon"),t=null,a=null,o=!0;async function r(r,l){if(o){o=!1,r?.now_playing&&(t=`${r.now_playing.three_line?.line1||""}|${r.now_playing.image_key||""}`,a=r.state);return}if(!r?.now_playing)return;let c=`${r.now_playing.three_line?.line1||""}|${r.now_playing.image_key||""}`,g="playing"===r.state,p=a,s=c!==t;if(!(g&&"playing"!==p||s&&g)){a=r.state;return}try{let o=(0,n.parseNowPlaying)(r.now_playing),g=null;if(r.now_playing.image_key&&l?.services?.RoonApiImage)try{g=await (0,i.cacheImage)(l.services.RoonApiImage,r.now_playing.image_key)}catch(e){g=null}let p=["--app-name=Roon","--icon=audio-x-generic","--replace-id=1","--hint=int:transient:1",...g?[`--hint=string:image-path:${g}`]:[],"--expire-time=5000",o.title,`${o.artists.join(", ")} • ${o.album}`];console.log(`Showing notification for track: ${o.title}`),(0,e.execSync)(`notify-send ${p.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" ")}`),t=c,a=r.state}catch(e){console.error("Failed to show notification:",e)}}
package/dist/roon.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get getCore(){return w},get getKnownActions(){return g},get getZone(){return p},get initRoon(){return m},get parseNowPlaying(){return d},get playItem(){return h},get searchRoon(){return k}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let n=/*#__PURE__*/u(require("node-roon-api")),o=/*#__PURE__*/u(require("node-roon-api-browse")),a=/*#__PURE__*/u(require("node-roon-api-image")),r=/*#__PURE__*/u(require("node-roon-api-transport")),l=require("./frequency"),s=require("./image-cache");function u(e){return e&&e.__esModule?e:{default:e}}let c=null,_=null,d=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 m(e){let t=new n.default({extension_id:"com.bluemancz.roonpipe",display_name:"RoonPipe",display_version:"1.0.11",publisher:"BlueManCZ",email:"your@email.com",website:"https://github.com/bluemancz/roonpipe",log_level:"none",core_paired:t=>{_=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,_=null,e.onCoreUnpaired(t),console.log(`Core unpaired: ${t.display_name}`)}});t.init_services({required_services:[o.default,a.default,r.default]}),t.start_discovery()}function y(e,t){return new Promise((i,n)=>{e(t,(e,t)=>{e?n(e):i(t)})})}function f(e){return e?e.replace(/\[\[(\d+)\|([^\]]+)]]/g,"$2").trim():""}function g(e,t){let i=[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Play Album"},{title:"Start Radio"}],n=[{title:"Shuffle"},{title:"Start Radio"}];if("action_list"===t)return"track"===e?i:n;switch(e){case"album":return[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}];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[]}}async function k(e){if(!_)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let t=_.services.RoonApiBrowse,i=`search_${Date.now()}`,n=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:c.zone_id,...e}),o=await y(t.browse.bind(t),n({input:e})),a=await y(t.load.bind(t),n({input:e,offset:0,count:o.list.count})),r=[];for(let e of a.items){if(!e.title)continue;let i=n({item_key:e.item_key}),o=await y(t.browse.bind(t),i),a=await y(t.load.bind(t),{...i,offset:0,count:Math.min(o.list.count,5)}),l=a.items?.map(e=>e.image_key).filter(Boolean)||[],u=await (0,s.cacheImages)(_.services.RoonApiImage,l),c=e.title.toLowerCase(),d=c.includes("composer")||c.includes("artist");r.push({category:e,items:a.items||[],cachedImages:u,isArtistCategory:d})}let u=new Map;for(let{items:e,cachedImages:t,isArtistCategory:i}of r)if(i)for(let i of e){let e=t.get(i.image_key);i.title&&e&&u.set(i.title,{path:e,key:i.image_key})}let d=[],m=new Set;for(let{category:e,items:t,cachedImages:n,isArtistCategory:o}of r){if(o)continue;let a=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++){let r,l=t[o];if("No Results"===l.title)continue;let s="action_list"===l.hint&&"Play Artist"===l.title,c=g(r=s?"artist":"track"===a&&"list"===l.hint?"album":a,l.hint),_=`${l.title}::${l.image_key}`;if(m.has(_))continue;m.add(_);let y=s?e.title:null,k=y?u.get(y):null,h=n.get(l.image_key)||k?.path||null;if(d.push({title:s?e.title:l.title||`Unknown ${r.charAt(0).toUpperCase()+r.slice(1)}`,subtitle:f(l.subtitle),item_key:l.item_key,image:h,image_key:l.image_key||k?.key||"",hint:l.hint,sessionKey:i,type:r,category_key:e.item_key,index:o,actions:c}),s)break}}let k=(0,l.reRankResults)(e,d),h=k.filter(e=>"stored"===e.sessionKey&&!e.image&&e.image_key).map(e=>e.image_key);if(h.length>0){let e=await (0,s.cacheImages)(_.services.RoonApiImage,h);for(let t of k)"stored"===t.sessionKey&&!t.image&&t.image_key&&(t.image=e.get(t.image_key)||null)}return k}async function h(e,t,i,n,o,a,r,s){if(!_)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let u=_.services.RoonApiBrowse,d=(e,t={})=>({hierarchy:"search",multi_session_key:e,zone_or_output_id:c.zone_id,...t});if("stored"===t&&a){console.log(`[DEBUG] Resolving stored item: "${a}" (image_key: ${s})`);let e=(await k(a)).find(e=>e.image_key===s&&"stored"!==e.sessionKey);if(!e)throw Error(`Could not find "${a}" in fresh search results`);return h(e.item_key,e.sessionKey,e.category_key,e.index,o,e.title,e.type,e.image_key)}console.log(`[DEBUG] playItem: itemKey=${e}, categoryKey=${i}, itemIndex=${n}, actionTitle=${o}`),await y(u.browse.bind(u),d(t,{item_key:i}));let m=await y(u.load.bind(u),d(t,{item_key:i,offset:n,count:1}));if(!m.items?.[0])throw Error("Item not found at index");let g=m.items[0],p=g.item_key;if(console.log(`[DEBUG] Got fresh item_key: ${p}`),"Play Album"===o){let e=g.image_key;if(!e)throw Error("Track has no image_key — cannot identify album");let t=g.subtitle||"",i=[...t.split(",").map(e=>e.trim()).filter(Boolean)].sort((e,t)=>e.split(/\s+/).length-t.split(/\s+/).length),n=`album_${Date.now()}`,o=(e={})=>({hierarchy:"search",multi_session_key:n,zone_or_output_id:c.zone_id,...e});for(let n of i){console.log(`[DEBUG] Trying album search with artist: "${n}" (track image_key: ${e})`);try{let i=await y(u.browse.bind(u),o({input:n})),c=await y(u.load.bind(u),o({offset:0,count:i.list.count})),_=c.items?.find(e=>e.title?.toLowerCase()==="albums");if(!_)continue;let d=await y(u.browse.bind(u),o({item_key:_.item_key})),m=await y(u.load.bind(u),o({offset:0,count:Math.min(d.list.count,50)})),k=m.items?.find(t=>t.image_key===e);if(!k)continue;console.log(`[DEBUG] Found matching album: "${k.title}" (via "${n}")`),await y(u.browse.bind(u),o({item_key:k.item_key}));let h=await y(u.load.bind(u),o({offset:0,count:5})),p=h.items?.find(e=>"list"===e.hint);if(!p)continue;await y(u.browse.bind(u),o({item_key:p.item_key}));let w=await y(u.load.bind(u),o({offset:0,count:30})),b=w.items?.find(e=>"action_list"===e.hint&&"Play Album"===e.title);if(!b)continue;await y(u.browse.bind(u),o({item_key:b.item_key}));let $=await y(u.load.bind(u),o({offset:0,count:10})),E=$.items?.find(e=>"action"===e.hint&&"Play Now"===e.title);if(!E)continue;console.log(`[DEBUG] Playing album: "${k.title}"`),await y(u.browse.bind(u),o({item_key:E.item_key})),console.log("[DEBUG] Successfully started album playback"),(0,l.recordPlay)({title:a||g.title||"",subtitle:f(t),item_key:"",image:null,image_key:g.image_key||s||"",hint:"",sessionKey:"",type:r||"track",category_key:"",index:0,actions:[]});return}catch{}}throw Error("Could not find album for this track")}async function w(e,t,i=0){if(i>5)return!1;let n=await y(u.browse.bind(u),d(t,{item_key:e})),a=n.list?.multi_session_key||t,r=await y(u.load.bind(u),d(a,{item_key:e,offset:0,count:n.list?.count||50}));if(!r.items?.length)return!1;for(let e of r.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 y(u.browse.bind(u),d(a,{item_key:e.item_key})),console.log("[DEBUG] Successfully executed action"),!0;if("action_list"===e.hint||"list"===e.hint&&1===r.items.length){if(await w(e.item_key,a,i+1))return!0;if(1===i&&"action_list"===e.hint)break}}return!1}if(!await w(p,t))throw Error(`Could not find action "${o}" to execute`);(0,l.recordPlay)({title:a||g.title||"",subtitle:f(g.subtitle||""),item_key:"",image:null,image_key:g.image_key||s||"",hint:"",sessionKey:"",type:r||"track",category_key:"",index:0,actions:[]})}function p(){return c}function w(){return _}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get getCore(){return w},get getKnownActions(){return g},get getZone(){return p},get initRoon(){return m},get parseNowPlaying(){return _},get playItem(){return k},get searchRoon(){return h}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let n=/*#__PURE__*/c(require("node-roon-api")),o=/*#__PURE__*/c(require("node-roon-api-browse")),a=/*#__PURE__*/c(require("node-roon-api-image")),r=/*#__PURE__*/c(require("node-roon-api-transport")),l=require("./frequency"),s=require("./image-cache");function c(e){return e&&e.__esModule?e:{default:e}}let u=null,d=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 m(e){let t=new n.default({extension_id:"com.bluemancz.roonpipe",display_name:"RoonPipe",display_version:"1.0.13",publisher:"BlueManCZ",email:"your@email.com",website:"https://github.com/bluemancz/roonpipe",log_level:"none",core_paired:t=>{d=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_added&&!u&&(u=n.zones_added.find(e=>"playing"===e.state)||n.zones_added[0],e.onZoneChanged(u,t)),n.zones_changed){let i=n.zones_changed.find(e=>"playing"===e.state);u=i||(u?n.zones_changed.find(e=>e.zone_id===u.zone_id)||u:n.zones_changed[0]),e.onZoneChanged(u,t)}if(n.zones_seek_changed){let i=n.zones_seek_changed.find(e=>e.zone_id===u?.zone_id);i&&u?.now_playing&&(u.now_playing.seek_position=i.seek_position,e.onSeekChanged(1e6*i.seek_position),n.zones_changed||"playing"===u.state||(u.state="playing",e.onZoneChanged(u,t)))}}}),e.onCorePaired(t),console.log(`Core paired: ${t.display_name}`)},core_unpaired:i=>{u=null,d=null,e.onCoreUnpaired(i),console.log(`Core unpaired: ${i.display_name}`),t.scan_count=-1},moo_onerror:e=>{console.error(`Roon API connection error (${e?.transport?.host}:${e?.transport?.port})`)}});t.init_services({required_services:[o.default,a.default,r.default]}),t.start_discovery()}function y(e,t){return new Promise((i,n)=>{e(t,(e,t)=>{e?n(e):i(t)})})}function f(e){return e?e.replace(/\[\[(\d+)\|([^\]]+)]]/g,"$2").trim():""}function g(e,t){let i=[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Play Album"},{title:"Start Radio"}],n=[{title:"Shuffle"},{title:"Start Radio"}];if("action_list"===t)return"track"===e?i:n;switch(e){case"album":return[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}];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[]}}async function h(e){if(!d)throw Error("Roon Core not connected");if(!u)throw Error("No active zone");let t=d.services.RoonApiBrowse,i=`search_${Date.now()}`,n=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:u.zone_id,...e}),o=await y(t.browse.bind(t),n({input:e})),a=await y(t.load.bind(t),n({input:e,offset:0,count:o.list.count})),r=[];for(let e of a.items){if(!e.title)continue;let i=n({item_key:e.item_key}),o=await y(t.browse.bind(t),i),a=await y(t.load.bind(t),{...i,offset:0,count:Math.min(o.list.count,5)}),l=a.items?.map(e=>e.image_key).filter(Boolean)||[],c=await (0,s.cacheImages)(d.services.RoonApiImage,l),u=e.title.toLowerCase(),_=u.includes("composer")||u.includes("artist");r.push({category:e,items:a.items||[],cachedImages:c,isArtistCategory:_})}let c=new Map;for(let{items:e,cachedImages:t,isArtistCategory:i}of r)if(i)for(let i of e){let e=t.get(i.image_key);i.title&&e&&c.set(i.title,{path:e,key:i.image_key})}let _=[],m=new Set;for(let{category:e,items:t,cachedImages:n,isArtistCategory:o}of r){if(o)continue;let a=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++){let r,l=t[o];if("No Results"===l.title)continue;let s="action_list"===l.hint&&"Play Artist"===l.title,u=g(r=s?"artist":"track"===a&&"list"===l.hint?"album":a,l.hint),d=`${l.title}::${l.image_key}`;if(m.has(d))continue;m.add(d);let y=s?e.title:null,h=y?c.get(y):null,k=n.get(l.image_key)||h?.path||null;if(_.push({title:s?e.title:l.title||`Unknown ${r.charAt(0).toUpperCase()+r.slice(1)}`,subtitle:f(l.subtitle),item_key:l.item_key,image:k,image_key:l.image_key||h?.key||"",hint:l.hint,sessionKey:i,type:r,category_key:e.item_key,index:o,actions:u}),s)break}}let h=(0,l.reRankResults)(e,_),k=h.filter(e=>"stored"===e.sessionKey&&!e.image&&e.image_key).map(e=>e.image_key);if(k.length>0){let e=await (0,s.cacheImages)(d.services.RoonApiImage,k);for(let t of h)"stored"===t.sessionKey&&!t.image&&t.image_key&&(t.image=e.get(t.image_key)||null)}return h}async function k(e,t,i,n,o,a,r,s){if(!d)throw Error("Roon Core not connected");if(!u)throw Error("No active zone");let c=d.services.RoonApiBrowse,_=(e,t={})=>({hierarchy:"search",multi_session_key:e,zone_or_output_id:u.zone_id,...t});if("stored"===t&&a){console.log(`[DEBUG] Resolving stored item: "${a}" (image_key: ${s})`);let e=(await h(a)).find(e=>e.image_key===s&&"stored"!==e.sessionKey);if(!e)throw Error(`Could not find "${a}" in fresh search results`);return k(e.item_key,e.sessionKey,e.category_key,e.index,o,e.title,e.type,e.image_key)}console.log(`[DEBUG] playItem: itemKey=${e}, categoryKey=${i}, itemIndex=${n}, actionTitle=${o}`),await y(c.browse.bind(c),_(t,{item_key:i}));let m=await y(c.load.bind(c),_(t,{item_key:i,offset:n,count:1}));if(!m.items?.[0])throw Error("Item not found at index");let g=m.items[0],p=g.item_key;if(console.log(`[DEBUG] Got fresh item_key: ${p}`),"Play Album"===o){let e=g.image_key;if(!e)throw Error("Track has no image_key — cannot identify album");let t=g.subtitle||"",i=[...t.split(",").map(e=>e.trim()).filter(Boolean)].sort((e,t)=>e.split(/\s+/).length-t.split(/\s+/).length),n=`album_${Date.now()}`,o=(e={})=>({hierarchy:"search",multi_session_key:n,zone_or_output_id:u.zone_id,...e});for(let n of i){console.log(`[DEBUG] Trying album search with artist: "${n}" (track image_key: ${e})`);try{let i=await y(c.browse.bind(c),o({input:n})),u=await y(c.load.bind(c),o({offset:0,count:i.list.count})),d=u.items?.find(e=>e.title?.toLowerCase()==="albums");if(!d)continue;let _=await y(c.browse.bind(c),o({item_key:d.item_key})),m=await y(c.load.bind(c),o({offset:0,count:Math.min(_.list.count,50)})),h=m.items?.find(t=>t.image_key===e);if(!h)continue;console.log(`[DEBUG] Found matching album: "${h.title}" (via "${n}")`),await y(c.browse.bind(c),o({item_key:h.item_key}));let k=await y(c.load.bind(c),o({offset:0,count:5})),p=k.items?.find(e=>"list"===e.hint);if(!p)continue;await y(c.browse.bind(c),o({item_key:p.item_key}));let w=await y(c.load.bind(c),o({offset:0,count:30})),b=w.items?.find(e=>"action_list"===e.hint&&"Play Album"===e.title);if(!b)continue;await y(c.browse.bind(c),o({item_key:b.item_key}));let $=await y(c.load.bind(c),o({offset:0,count:10})),z=$.items?.find(e=>"action"===e.hint&&"Play Now"===e.title);if(!z)continue;console.log(`[DEBUG] Playing album: "${h.title}"`),await y(c.browse.bind(c),o({item_key:z.item_key})),console.log("[DEBUG] Successfully started album playback"),(0,l.recordPlay)({title:a||g.title||"",subtitle:f(t),item_key:"",image:null,image_key:g.image_key||s||"",hint:"",sessionKey:"",type:r||"track",category_key:"",index:0,actions:[]});return}catch{}}throw Error("Could not find album for this track")}async function w(e,t,i=0){if(i>5)return!1;let n=await y(c.browse.bind(c),_(t,{item_key:e})),a=n.list?.multi_session_key||t,r=await y(c.load.bind(c),_(a,{item_key:e,offset:0,count:n.list?.count||50}));if(!r.items?.length)return!1;for(let e of r.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 y(c.browse.bind(c),_(a,{item_key:e.item_key})),console.log("[DEBUG] Successfully executed action"),!0;if("action_list"===e.hint||"list"===e.hint&&1===r.items.length){if(await w(e.item_key,a,i+1))return!0;if(1===i&&"action_list"===e.hint)break}}return!1}if(!await w(p,t))throw Error(`Could not find action "${o}" to execute`);(0,l.recordPlay)({title:a||g.title||"",subtitle:f(g.subtitle||""),item_key:"",image:null,image_key:g.image_key||s||"",hint:"",sessionKey:"",type:r||"track",category_key:"",index:0,actions:[]})}function p(){return u}function w(){return d}
package/dist/socket.js CHANGED
@@ -1,8 +1,8 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,r={get isInstanceRunning(){return l},get startSocketServer(){return a}};for(var t in r)Object.defineProperty(e,t,{enumerable:!0,get:Object.getOwnPropertyDescriptor(r,t).get});let n=/*#__PURE__*/s(require("node:fs")),i=/*#__PURE__*/s(require("node:net")),o=require("./frequency");function s(e){return e&&e.__esModule?e:{default:e}}let c="/tmp/roonpipe.sock";function l(){return new Promise(e=>{if(!n.default.existsSync(c))return void e(!1);let r=i.default.createConnection({path:c},()=>{r.end(),e(!0)});r.on("error",()=>{e(!1)}),r.setTimeout(1e3,()=>{r.destroy(),e(!1)})})}function a(e){n.default.existsSync(c)&&n.default.unlinkSync(c),i.default.createServer(r=>{console.log("Client connected to socket"),r.on("data",async t=>{try{let n=JSON.parse(t.toString());if(console.log("Received request:",n),"search"===n.command){try{let t=await e.search(n.query);r.write(`${JSON.stringify({error:null,results:t})}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,r={get isInstanceRunning(){return a},get startSocketServer(){return u}};for(var t in r)Object.defineProperty(e,t,{enumerable:!0,get:Object.getOwnPropertyDescriptor(r,t).get});let n=/*#__PURE__*/s(require("node:fs")),o=/*#__PURE__*/s(require("node:net")),i=require("./frequency");function s(e){return e&&e.__esModule?e:{default:e}}let c="/tmp/roonpipe.sock",l=null;function a(){return new Promise(e=>{if(!n.default.existsSync(c))return void e(!1);let r=o.default.createConnection({path:c},()=>{r.end(),e(!0)});r.on("error",()=>{e(!1)}),r.setTimeout(1e3,()=>{r.destroy(),e(!1)})})}function u(e){if(l){try{l.close()}catch(e){}l=null}n.default.existsSync(c)&&n.default.unlinkSync(c),(l=o.default.createServer(r=>{console.log("Client connected to socket"),r.on("data",async t=>{try{let n=JSON.parse(t.toString());if(console.log("Received request:",n),"search"===n.command){try{let t=await e.search(n.query);r.write(`${JSON.stringify({error:null,results:t})}
2
2
  `)}catch(e){r.write(`${JSON.stringify({error:String(e),results:null})}
3
3
  `)}r.end()}else if("play"===n.command){try{await e.play(n.item_key,n.session_key,n.category_key,n.item_index,n.action_title,n.item_title,n.item_type,n.item_image_key),r.write(`${JSON.stringify({error:null,success:!0})}
4
4
  `)}catch(e){r.write(`${JSON.stringify({error:String(e),success:!1})}
5
- `)}r.end()}else if("remove_frequency"===n.command){let e=(0,o.removeFrequencyEntry)(n.item_type,n.item_title,n.item_image_key);r.write(`${JSON.stringify({error:null,success:e})}
5
+ `)}r.end()}else if("remove_frequency"===n.command){let e=(0,i.removeFrequencyEntry)(n.item_type,n.item_title,n.item_image_key);r.write(`${JSON.stringify({error:null,success:e})}
6
6
  `),r.end()}else r.write(`${JSON.stringify({error:"Unknown command"})}
7
7
  `),r.end()}catch(e){console.error("Socket error:",e),r.write(`${JSON.stringify({error:"Invalid request format"})}
8
- `),r.end()}}),r.on("error",e=>{console.error("Client error:",e)})}).listen(c,()=>{console.log("Unix socket server listening on",c),n.default.chmodSync(c,438)})}
8
+ `),r.end()}}),r.on("error",e=>{console.error("Client error:",e)})})).on("error",e=>{console.error("Socket server error:",e)}),l.listen(c,()=>{console.log("Unix socket server listening on",c),n.default.chmodSync(c,438)})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roonpipe",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
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": {