roonpipe 1.0.9 โ†’ 1.0.11

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.
package/README.md CHANGED
@@ -40,8 +40,8 @@ Found 50 result(s):
40
40
 
41
41
  ? Select an item to play: ๐Ÿ’ฟ The Wall ยท Pink Floyd
42
42
  ? What do you want to do?
43
- โฏ โ–ถ๏ธ Play Now
44
- โญ๏ธ Add Next
43
+ โฏ โ–ถ๏ธ Play Now
44
+ โญ๏ธ Add Next
45
45
  ๐Ÿ“‹ Queue
46
46
  ๐Ÿ“ป Start Radio
47
47
  ```
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"startCLI",{enumerable:!0,get:function(){return u}});let e=/*#__PURE__*/o(require("node:net")),t=/*#__PURE__*/o(require("node:readline")),n=require("@inquirer/prompts");function o(e){return e&&e.__esModule?e:{default:e}}function r(t){return new Promise((n,o)=>{let r=e.default.createConnection("/tmp/roonpipe.sock",()=>{r.write(JSON.stringify(t))}),a="";r.on("data",e=>{a+=e.toString()}),r.on("end",()=>{try{let e=JSON.parse(a);e.error?o(e.error):n(e)}catch{o("Failed to parse response")}}),r.on("error",e=>{o(`Cannot connect to RoonPipe daemon. Is it running?
2
- ${e.message}`)})})}async function a(){let e=t.default.createInterface({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("๐Ÿ” Search: ",n=>{e.close(),t(n)})})}async function i(){let e=await a();if(!e.trim())return[];console.log(`
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"startCLI",{enumerable:!0,get:function(){return u}});let e=/*#__PURE__*/n(require("node:net")),t=/*#__PURE__*/n(require("node:readline")),o=require("@inquirer/prompts");function n(e){return e&&e.__esModule?e:{default:e}}function r(t){return new Promise((o,n)=>{let r=e.default.createConnection("/tmp/roonpipe.sock",()=>{r.write(JSON.stringify(t))}),i="";r.on("data",e=>{i+=e.toString()}),r.on("end",()=>{try{let e=JSON.parse(i);e.error?n(e.error):o(e)}catch{n("Failed to parse response")}}),r.on("error",e=>{n(`Cannot connect to RoonPipe daemon. Is it running?
2
+ ${e.message}`)})})}async function i(){let e=t.default.createInterface({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("๐Ÿ” Search: ",o=>{e.close(),t(o)})})}async function a(){let e=await i();if(!e.trim())return[];console.log(`
3
3
  Searching for "${e}"...
4
- `);try{return(await r({command:"search",query:e})).results||[]}catch(e){return console.error("โŒ Error:",e),[]}}async function l(e){let t={track:"๐ŸŽต",album:"๐Ÿ’ฟ",artist:"๐ŸŽค",playlist:"๐Ÿ“‹",work:"๐ŸŽผ",composer:"๐Ÿ‘ค"},o=[...e.map((e,n)=>({name:`${t[e.type]||"โ€ข"} ${e.title} ${e.subtitle?`\xb7 ${e.subtitle}`:""}`,value:n})),new n.Separator,{name:"๐Ÿ” New search",value:-1},{name:"โŒ Quit",value:-2}];try{let t=await (0,n.select)({message:"Select an item to play:",choices:o,pageSize:15,theme:{prefix:""}});if(-2===t)return null;if(-1===t)return{item_key:"",image_key:"",sessionKey:"",title:"",subtitle:"__search__",type:"track",category_key:"",index:0,actions:[]};return e[t]}catch{return null}}async function s(e){try{let t={"Play Now":"โ–ถ๏ธ","Play Album":"๐Ÿ’ฟ",Play:"โ–ถ๏ธ",Shuffle:"๐Ÿ”€",Queue:"๐Ÿ“‹","Add to Queue":"๐Ÿ“‹","Add Next":"โญ๏ธ","Play From Here":"โญ๏ธ","Start Radio":"๐Ÿ“ป"},o=e.map(e=>({name:`${t[e.title]||"โ€ข"} ${e.title}`,value:e})),r=await (0,n.select)({message:"What do you want to do?",choices:[...o,new n.Separator,{name:"โ† Back",value:"back"}],theme:{prefix:""}});return"back"===r?null:r}catch{return null}}async function c(e,t){console.log(`
4
+ `);try{return(await r({command:"search",query:e})).results||[]}catch(e){return console.error("โŒ Error:",e),[]}}async function l(e){let t={track:"๐ŸŽต",album:"๐Ÿ’ฟ",artist:"๐ŸŽค",playlist:"๐Ÿ“‹",work:"๐ŸŽผ",composer:"๐Ÿ‘ค"},n=[...e.map((e,o)=>({name:`${t[e.type]||"โ€ข"} ${e.title} ${e.subtitle?`\xb7 ${e.subtitle}`:""}`,value:o})),new o.Separator,{name:"๐Ÿ” New search",value:-1},{name:"โŒ Quit",value:-2}];try{let t=await (0,o.select)({message:"Select an item to play:",choices:n,pageSize:15,theme:{prefix:""}});if(-2===t)return null;if(-1===t)return{item_key:"",image_key:"",sessionKey:"",title:"",subtitle:"__search__",type:"track",category_key:"",index:0,actions:[]};return e[t]}catch{return null}}async function c(e){try{let t={"Play Now":"โ–ถ๏ธ","Play Album":"๐Ÿ’ฟ",Play:"โ–ถ๏ธ",Shuffle:"๐Ÿ”€",Queue:"๐Ÿ“‹","Add to Queue":"๐Ÿ“‹","Add Next":"โญ๏ธ","Play From Here":"โญ๏ธ","Start Radio":"๐Ÿ“ป","Remove from History":"๐Ÿ—‘๏ธ"},n=e.map(e=>({name:`${t[e.title]||"โ€ข"} ${e.title}`,value:e})),r=await (0,o.select)({message:"What do you want to do?",choices:[...n,new o.Separator,{name:"โ† Back",value:"back"}],theme:{prefix:""}});return"back"===r?null:r}catch{return null}}async function s(e,t){console.log(`
5
5
  ${t.title}: ${e.title}${e.subtitle?` \xb7 ${e.subtitle}`:""}
6
- `);try{await r({command:"play",item_key:e.item_key,session_key:e.sessionKey,category_key:e.category_key,item_index:e.index,action_title:t.title,item_title:e.title,item_type:e.type,item_image_key:e.image_key}),console.log("โœ… Success!\n")}catch(e){console.error("โŒ Failed:",e)}}async function u(){for(console.log("\n๐ŸŽต RoonPipe Interactive Search"),console.log("==============================\n");;){let e=await i();if(!e.length){console.log("โŒ No results found.\n");continue}console.log(`Found ${e.length} result(s):
7
- `);let t=await l(e);if(!t){console.log("\nGoodbye! ๐Ÿ‘‹\n");break}if("__search__"===t.subtitle)continue;if(0===t.actions.length){console.log("No actions available for this item.\n");continue}let n=await s(t.actions);n&&await c(t,n)}}
6
+ `);try{await r({command:"play",item_key:e.item_key,session_key:e.sessionKey,category_key:e.category_key,item_index:e.index,action_title:t.title,item_title:e.title,item_type:e.type,item_image_key:e.image_key}),console.log("โœ… Success!\n")}catch(e){console.error("โŒ Failed:",e)}}async function u(){for(console.log("\n๐ŸŽต RoonPipe Interactive Search"),console.log("==============================\n");;){let e=await a();if(!e.length){console.log("โŒ No results found.\n");continue}console.log(`Found ${e.length} result(s):
7
+ `);let t=await l(e);if(!t){console.log("\nGoodbye! ๐Ÿ‘‹\n");break}if("__search__"===t.subtitle)continue;if(0===t.actions.length){console.log("No actions available for this item.\n");continue}let o=await c(t.actions);if(o){if("remove_frequency"===o.command){try{await r({command:"remove_frequency",item_title:t.title,item_type:t.type,item_image_key:t.image_key}),console.log("โœ… Removed from history.\n")}catch(e){console.error("โŒ Failed:",e)}continue}await s(t,o)}}}
package/dist/frequency.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get loadFrequencyData(){return d},get reRankResults(){return m},get recordPlay(){return f}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let r=/*#__PURE__*/n(require("node:fs")),a=/*#__PURE__*/n(require("node:os")),l=/*#__PURE__*/n(require("node:path")),o=require("./image-cache");function n(e){return e&&e.__esModule?e:{default:e}}let s=l.default.join(a.default.homedir(),".cache","roonpipe","frequency.json"),u={version:1,items:{}},c=null;function y(){try{let e=l.default.dirname(s);r.default.existsSync(e)||r.default.mkdirSync(e,{recursive:!0}),r.default.writeFileSync(s,JSON.stringify(u,null,2))}catch(e){console.error("Failed to save frequency data:",e)}}function d(){try{if(r.default.existsSync(s)){let e=JSON.parse(r.default.readFileSync(s,"utf-8"));if(e?.version===1&&e?.items){u=e;let t=Date.now()-15552e6;for(let[e,i]of Object.entries(u.items))i.lastPlayed<t&&delete u.items[e];y()}}}catch(e){console.error("Failed to load frequency data, starting fresh:",e),u={version:1,items:{}}}}function f(e){try{var t,i,r;let a=(t=e.type,i=e.title,r=e.image_key,`${t}::${i}::${r}`),l=u.items[a];u.items[a]={title:e.title,subtitle:e.subtitle,type:e.type,image:e.image,image_key:e.image_key,count:(l?.count||0)+1,lastPlayed:Date.now()},c&&clearTimeout(c),c=setTimeout(y,2e3)}catch(e){console.error("Failed to record play:",e)}}function m(e,t){try{let i=e.toLowerCase(),r=new Set(t.map(e=>`${e.title}::${e.image_key}`)),a=[];for(let[,e]of Object.entries(u.items)){if(!e.title.toLowerCase().includes(i))continue;let t=`${e.title}::${e.image_key}`;if(r.has(t))continue;let l=e.image_key&&(0,o.isImageCached)(e.image_key)?(0,o.getImageCachePath)(e.image_key):e.image;a.push({title:e.title,subtitle:e.subtitle,item_key:"",image:l,image_key:e.image_key,hint:"",sessionKey:"stored",type:e.type,category_key:"",index:0,actions:"track"===e.type?[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Play Album"},{title:"Start Radio"}]:"album"===e.type?[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}]:"artist"===e.type||"composer"===e.type?[{title:"Shuffle"},{title:"Start Radio"}]:[]})}let l=new Map;for(let[,e]of Object.entries(u.items)){let t=`${e.title}::${e.image_key}`;l.set(t,function(e){let t=(Date.now()-e.lastPlayed)/864e5;return Math.log2(1+e.count)*.5**(t/30)}(e))}let n=[...t,...a],s=n.length;return n.sort((e,i)=>{let r=`${e.title}::${e.image_key}`,a=`${i.title}::${i.image_key}`,o=l.get(r)||0,n=l.get(a)||0,u=t.indexOf(e),c=t.indexOf(i);return(c>=0?.5*(1-c/s):0)+n-((u>=0?.5*(1-u/s):0)+o)}),n}catch(e){return console.error("Failed to re-rank results:",e),t}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,t={get loadFrequencyData(){return f},get reRankResults(){return p},get recordPlay(){return g},get removeFrequencyEntry(){return _}};for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,r).get});let i=/*#__PURE__*/s(require("node:fs")),n=/*#__PURE__*/s(require("node:os")),o=/*#__PURE__*/s(require("node:path")),a=require("./image-cache"),l=require("./roon");function s(e){return e&&e.__esModule?e:{default:e}}let c=o.default.join(n.default.homedir(),".cache","roonpipe","frequency.json"),u={version:1,items:{}},y=null;function m(e,t,r){return`${e}::${t}::${r}`}function d(){try{let e=o.default.dirname(c);i.default.existsSync(e)||i.default.mkdirSync(e,{recursive:!0}),i.default.writeFileSync(c,JSON.stringify(u,null,2))}catch(e){console.error("Failed to save frequency data:",e)}}function f(){try{if(i.default.existsSync(c)){let e=JSON.parse(i.default.readFileSync(c,"utf-8"));if(e?.version===1&&e?.items){u=e;let t=Date.now()-15552e6;for(let[e,r]of Object.entries(u.items))r.lastPlayed<t&&delete u.items[e];d()}}}catch(e){console.error("Failed to load frequency data, starting fresh:",e),u={version:1,items:{}}}}function g(e){try{let t=m(e.type,e.title,e.image_key),r=u.items[t];u.items[t]={title:e.title,subtitle:e.subtitle,type:e.type,image:e.image,image_key:e.image_key,count:(r?.count||0)+1,lastPlayed:Date.now()},y&&clearTimeout(y),y=setTimeout(d,2e3)}catch(e){console.error("Failed to record play:",e)}}function _(e,t,r){try{let i=m(e,t,r);if(i in u.items)return delete u.items[i],d(),!0;return!1}catch(e){return console.error("Failed to remove frequency entry:",e),!1}}function p(e,t){try{let r=e.toLowerCase(),i=new Set(t.map(e=>`${e.title}::${e.image_key}`)),n={title:"Remove from History",command:"remove_frequency"},o=[];for(let[,e]of Object.entries(u.items)){if(!e.title.toLowerCase().includes(r))continue;let t=`${e.title}::${e.image_key}`;if(i.has(t))continue;let s=e.image_key&&(0,a.isImageCached)(e.image_key)?(0,a.getImageCachePath)(e.image_key):e.image;o.push({title:e.title,subtitle:e.subtitle,item_key:"",image:s,image_key:e.image_key,hint:"",sessionKey:"stored",type:e.type,category_key:"",index:0,actions:[...(0,l.getKnownActions)(e.type,"action_list"),n]})}let s=new Map,c=new Set;for(let[,e]of Object.entries(u.items)){let t=`${e.title}::${e.image_key}`;s.set(t,function(e){let t=(Date.now()-e.lastPlayed)/864e5;return Math.log2(1+e.count)*.5**(t/30)}(e)),c.add(t)}for(let e of t)c.has(`${e.title}::${e.image_key}`)&&(e.actions=[...e.actions,n]);let y=[...t,...o],m=y.length;return y.sort((e,r)=>{let i=`${e.title}::${e.image_key}`,n=`${r.title}::${r.image_key}`,o=s.get(i)||0,a=s.get(n)||0,l=t.indexOf(e),c=t.indexOf(r);return(c>=0?.5*(1-c/m):0)+a-((l>=0?.5*(1-l/m):0)+o)}),y}catch(e){return console.error("Failed to re-rank results:",e),t}}
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 getZone(){return k},get initRoon(){return m},get parseNowPlaying(){return _},get playItem(){return h},get searchRoon(){return g}};for(var i in t)Object.defineProperty(e,i,{enumerable:!0,get:Object.getOwnPropertyDescriptor(t,i).get});let o=/*#__PURE__*/u(require("node-roon-api")),n=/*#__PURE__*/u(require("node-roon-api-browse")),r=/*#__PURE__*/u(require("node-roon-api-image")),a=/*#__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,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 o.default({extension_id:"com.bluemancz.roonpipe",display_name:"RoonPipe",display_version:"1.0.9",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,o)=>{if("Subscribed"===i)c=o.zones.find(e=>"playing"===e.state)||o.zones[0],e.onZoneChanged(c,t);else if("Changed"===i){if(o.zones_changed){let i=o.zones_changed.find(e=>"playing"===e.state);i?c=i:c&&(c=o.zones_changed.find(e=>e.zone_id===c.zone_id)||c),e.onZoneChanged(c,t)}if(o.zones_seek_changed){let i=o.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),o.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,d=null,e.onCoreUnpaired(t),console.log(`Core unpaired: ${t.display_name}`)}});t.init_services({required_services:[n.default,r.default,a.default]}),t.start_discovery()}function y(e,t){return new Promise((i,o)=>{e(t,(e,t)=>{e?o(e):i(t)})})}function f(e){return e?e.replace(/\[\[(\d+)\|([^\]]+)]]/g,"$2").trim():""}async function g(e){if(!d)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let t=d.services.RoonApiBrowse,i=`search_${Date.now()}`,o=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:c.zone_id,...e}),n=await y(t.browse.bind(t),o({input:e})),r=await y(t.load.bind(t),o({input:e,offset:0,count:n.list.count})),a=[];for(let e of r.items){if(!e.title)continue;let i=o({item_key:e.item_key}),n=await y(t.browse.bind(t),i),r=await y(t.load.bind(t),{...i,offset:0,count:Math.min(n.list.count,5)}),l=r.items?.map(e=>e.image_key).filter(Boolean)||[],u=await (0,s.cacheImages)(d.services.RoonApiImage,l),c=e.title.toLowerCase(),_=c.includes("composer")||c.includes("artist");a.push({category:e,items:r.items||[],cachedImages:u,isArtistCategory:_})}let u=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&&u.set(i.title,e)}let _=[],m=new Set;for(let{category:e,items:t,cachedImages:o,isArtistCategory:n}of a){if(n)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 n=0;n<t.length;n++){let a=t[n],l="action_list"===a.hint&&"Play Artist"===a.title,s=l?"artist":r,c=function(e,t){let i=[{title:"Play Now"},{title:"Add Next"},{title:"Queue"},{title:"Play Album"},{title:"Start Radio"}],o=[{title:"Shuffle"},{title:"Start Radio"}];if("action_list"===t)return"track"===e?i:o;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 o;case"playlist":return[{title:"Play Now"},{title:"Shuffle"},{title:"Add Next"},{title:"Queue"},{title:"Start Radio"}];default:return[]}}(s,a.hint),d=`${a.title}::${a.image_key}`;if(m.has(d))continue;m.add(d);let y=l?e.title:null,g=o.get(a.image_key)||y&&u.get(y)||null;if(_.push({title:l?e.title:a.title||`Unknown ${s.charAt(0).toUpperCase()+s.slice(1)}`,subtitle:f(a.subtitle),item_key:a.item_key,image:g,image_key:a.image_key||"",hint:a.hint,sessionKey:i,type:s,category_key:e.item_key,index:n,actions:c}),l)break}}return(0,l.reRankResults)(e,_)}async function h(e,t,i,o,n,r,a,s){if(!d)throw Error("Roon Core not connected");if(!c)throw Error("No active zone");let u=d.services.RoonApiBrowse,_=(e,t={})=>({hierarchy:"search",multi_session_key:e,zone_or_output_id:c.zone_id,...t});if("stored"===t&&r){console.log(`[DEBUG] Resolving stored item: "${r}" (image_key: ${s})`);let e=(await g(r)).find(e=>e.image_key===s&&"stored"!==e.sessionKey);if(!e)throw Error(`Could not find "${r}" in fresh search results`);return h(e.item_key,e.sessionKey,e.category_key,e.index,n,e.title,e.type,e.image_key)}console.log(`[DEBUG] playItem: itemKey=${e}, categoryKey=${i}, itemIndex=${o}, actionTitle=${n}`),await y(u.browse.bind(u),_(t,{item_key:i}));let m=await y(u.load.bind(u),_(t,{item_key:i,offset:o,count:1}));if(!m.items?.[0])throw Error("Item not found at index");let k=m.items[0],w=k.item_key;if(console.log(`[DEBUG] Got fresh item_key: ${w}`),"Play Album"===n){let e=k.image_key;if(!e)throw Error("Track has no image_key โ€” cannot identify album");let t=k.subtitle?.split(", ")[0]?.trim();if(!t)throw Error("Track has no subtitle โ€” cannot determine artist");let i=`album_${Date.now()}`,o=(e={})=>({hierarchy:"search",multi_session_key:i,zone_or_output_id:c.zone_id,...e});console.log(`[DEBUG] Searching for album by artist: "${t}" (track image_key: ${e})`);let n=await y(u.browse.bind(u),o({input:t})),d=await y(u.load.bind(u),o({offset:0,count:n.list.count})),_=d.items?.find(e=>e.title?.toLowerCase()==="albums");if(!_)throw Error("No Albums category found in search results");let m=await y(u.browse.bind(u),o({item_key:_.item_key})),g=await y(u.load.bind(u),o({offset:0,count:Math.min(m.list.count,20)})),h=g.items?.find(t=>t.image_key===e);if(!h)throw Error("Could not find album matching this track's artwork");console.log(`[DEBUG] Found matching album: "${h.title}"`),await y(u.browse.bind(u),o({item_key:h.item_key}));let w=await y(u.load.bind(u),o({offset:0,count:5})),b=w.items?.find(e=>"list"===e.hint);if(!b)throw Error("Could not find album list item");await y(u.browse.bind(u),o({item_key:b.item_key}));let p=await y(u.load.bind(u),o({offset:0,count:30})),E=p.items?.find(e=>"action_list"===e.hint&&"Play Album"===e.title);if(!E)throw Error('Could not find "Play Album" action on the album');await y(u.browse.bind(u),o({item_key:E.item_key}));let $=await y(u.load.bind(u),o({offset:0,count:10})),P=$.items?.find(e=>"action"===e.hint&&"Play Now"===e.title);if(!P)throw Error('Could not find "Play Now" action on album');console.log(`[DEBUG] Playing album: "${h.title}"`),await y(u.browse.bind(u),o({item_key:P.item_key})),console.log("[DEBUG] Successfully started album playback"),(0,l.recordPlay)({title:k.title||r||"",subtitle:f(k.subtitle||""),item_key:"",image:null,image_key:k.image_key||s||"",hint:"",sessionKey:"",type:a||"track",category_key:"",index:0,actions:[]});return}async function b(e,t,i=0){if(i>5)return!1;let o=await y(u.browse.bind(u),_(t,{item_key:e})),r=o.list?.multi_session_key||t,a=await y(u.load.bind(u),_(r,{item_key:e,offset:0,count:o.list?.count||50}));if(!a.items?.length)return!1;for(let e of a.items){if(console.log(`[DEBUG] Navigating: title=${e.title}, hint=${e.hint}`),"action"===e.hint&&e.title===n)return console.log(`[DEBUG] Found action! Executing: ${e.title} (${e.item_key})`),await y(u.browse.bind(u),_(r,{item_key:e.item_key})),console.log("[DEBUG] Successfully executed action"),!0;if("action_list"===e.hint||"list"===e.hint&&1===a.items.length){if(await b(e.item_key,r,i+1))return!0;if(1===i&&"action_list"===e.hint)break}}return!1}if(!await b(w,t))throw Error(`Could not find action "${n}" to execute`);(0,l.recordPlay)({title:k.title||r||"",subtitle:f(k.subtitle||""),item_key:"",image:null,image_key:k.image_key||s||"",hint:"",sessionKey:"",type:a||"track",category_key:"",index:0,actions:[]})}function k(){return c}function w(){return d}
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 _}
package/dist/socket.js CHANGED
@@ -1,7 +1,8 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=exports,r={get isInstanceRunning(){return c},get startSocketServer(){return l}};for(var t in r)Object.defineProperty(e,t,{enumerable:!0,get:Object.getOwnPropertyDescriptor(r,t).get});let n=/*#__PURE__*/i(require("node:fs")),o=/*#__PURE__*/i(require("node:net"));function i(e){return e&&e.__esModule?e:{default:e}}let s="/tmp/roonpipe.sock";function c(){return new Promise(e=>{if(!n.default.existsSync(s))return void e(!1);let r=o.default.createConnection({path:s},()=>{r.end(),e(!0)});r.on("error",()=>{e(!1)}),r.setTimeout(1e3,()=>{r.destroy(),e(!1)})})}function l(e){n.default.existsSync(s)&&n.default.unlinkSync(s),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})}
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})}
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 r.write(`${JSON.stringify({error:"Unknown command"})}
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})}
6
+ `),r.end()}else r.write(`${JSON.stringify({error:"Unknown command"})}
6
7
  `),r.end()}catch(e){console.error("Socket error:",e),r.write(`${JSON.stringify({error:"Invalid request format"})}
7
- `),r.end()}}),r.on("error",e=>{console.error("Client error:",e)})}).listen(s,()=>{console.log("Unix socket server listening on",s),n.default.chmodSync(s,438)})}
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)})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roonpipe",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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": {