@stacksjs/chat 0.75.121 → 0.75.122

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/dist/index.d.ts CHANGED
@@ -45,4 +45,4 @@ export * as teams from './drivers/teams';
45
45
  /** Reading and archiving conversations in iMessage, Slack and Discord. */
46
46
  export * as inbox from './inbox/index';
47
47
  /** Persistent first-party messaging between application users. */
48
- export { createMessenger, MessagingError } from '@stacksjs/orm';
48
+ export { createMessenger, messagingChannel, MessagingError } from '@stacksjs/orm';
package/dist/index.js CHANGED
@@ -90,4 +90,4 @@ end run`;async function ss(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...
90
90
  WHERE m.ZCHATSESSION = ? AND m.Z_PK > ?
91
91
  ORDER BY m.Z_PK ${s}
92
92
  ${r}
93
- `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:U(i.date)??0,senderJid:i.senderJid,senderName:f(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var ys="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",bs={[m.Image]:"image/jpeg",[m.Video]:"video/mp4",[m.Audio]:"audio/ogg",[m.Gif]:"video/mp4",[m.Sticker]:"image/webp"},vs={[m.Image]:"Photo",[m.Video]:"Video",[m.Audio]:"Voice message",[m.Document]:"Document",[m.Gif]:"GIF",[m.Sticker]:"Sticker"},ws={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp",".heic":"image/heic",".mp4":"video/mp4",".mov":"video/quicktime",".opus":"audio/ogg",".ogg":"audio/ogg",".m4a":"audio/mp4",".mp3":"audio/mpeg",".pdf":"application/pdf"};function Ss(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function Ze(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function As(e){let t=e?.match(/^FN:(.*)$/m);return f(t?.[1])}class ie{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??Le,this.mediaRoot=e.mediaRoot??(e.databasePath?Be(je(e.databasePath),"Message"):gt),this.controller=e.controller,this.openUrl=e.openUrl??Ss,this.platform=e.platform??fs.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=Be(je(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>be(s))??null}pictures(e){let t=e.profilePicturePaths(),s=new Map;return(r)=>{if(!s.has(r))s.set(r,this.pictureFile(t.get(r))?r:null);return s.get(r)}}async avatar(e){let t=this.open((s)=>this.pictureFile(s.profilePicturePaths().get(e)));return G(t?new Uint8Array(hs(t)):null)}open(e){let t=new We(this.databasePath);try{return e(t)}finally{t.close()}}async status(){if(this.platform!=="darwin")return{connected:!1,detail:"WhatsApp is read from WhatsApp for Mac, so it needs a Mac.",needs:"unsupported-platform"};try{return this.open(()=>{return}),{connected:!0,detail:"Reading WhatsApp on this Mac."}}catch(e){if(e instanceof ye&&e.needsFullDiskAccess&&be(je(this.databasePath)))return{connected:!1,detail:"Grant Full Disk Access in System Settings > Privacy & Security.",needs:"full-disk-access"};return{connected:!1,detail:"Install WhatsApp for Mac and link it to your phone.",needs:"signed-out"}}}archiveSupport(e){if(this.controller)return{mode:"native",detail:ys};return this.url(e)?{mode:"confirm",detail:"Opens the chat in WhatsApp for you to archive it there."}:{mode:"unsupported",detail:"WhatsApp cannot open a group by link, so archive it there yourself."}}url(e){let[t,s]=e.jid.split("@");return s==="s.whatsapp.net"&&t&&/^\d{7,15}$/.test(t)?`whatsapp://send?phone=${t}`:null}async conversations(){return this.open((e)=>{let t=this.pictures(e);return e.sessions().map((s)=>{let r=s.type===ne,n=r?[{id:s.jid,name:s.name,avatar:t(s.jid)}]:e.members(s.pk).map((i)=>({id:i.jid,name:i.name,avatar:t(i.jid)}));return{provider:"whatsapp",id:s.jid,kind:r?"direct":"group",title:r?null:s.name,participants:n,workspace:null,lastMessageAt:s.lastMessageAt,preview:s.lastMessageText,previewFromMe:s.lastMessageFromMe,unread:s.unread,cursor:String(s.lastMessagePk),visible:!s.archived,archive:this.archiveSupport(s),url:this.url(s),avatar:r?null:t(s.jid)}})})}async messages(e,t={}){return this.open((s)=>{let r=s.session(e);if(!r)return[];let n=t.after||!t.limit?s.messages(r.pk,{afterPk:Number(t.after)||0,limit:t.limit}):s.messages(r.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(s),a=new Map(r.type===ne?[]:s.members(r.pk).map((l)=>[l.jid,l.name]));return n.flatMap((l)=>[this.toMessage(r,l,i),...this.reactions(r,l,a,i)])})}toMessage(e,t,s){let r=t.isFromMe?null:e.type===ne?{id:e.jid,name:e.name,avatar:s(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:s(t.senderJid)}:null,{kind:n,text:i,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:n,text:i,fromMe:t.isFromMe,sentAt:t.sentAt,sender:r,targetId:null,reaction:null,reactionRemoved:!1,replyToId:re(se(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,s,r){let n=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Ue(se(t.receipts),7))Ue(se(a),1).forEach((l,o)=>{let d=se(l),c=re(d,3);if(!c)return;let h=re(d,2),p=!h&&$e(d,6)===1n,y=h??(p?null:e.jid),u=Number($e(d,4)??0n);i.push({id:re(d,1)??`${n}:reaction:${o}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:p,sentAt:u>0?u:t.sentAt,sender:y?{id:y,name:s.get(y)??(y===e.jid?e.name:null),avatar:r(y)}:null,targetId:n,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case m.Text:case m.Link:return{kind:"message",text:f(e.text),unsent:!1};case m.Image:case m.Video:case m.Gif:return{kind:"message",text:f(e.text)??f(t?.title),unsent:!1};case m.Contact:{let s=As(t?.vcard??null)??f(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case m.Location:{let s=f(t?.title),r=t?.latitude!=null&&t.longitude!=null&&(t.latitude!==0||t.longitude!==0)?`https://maps.apple.com/?ll=${t.latitude},${t.longitude}`:null;return{kind:"message",text:[s??"Shared a location",r].filter(Boolean).join(" "),unsent:!1}}case m.GroupEvent:case m.Call:return{kind:"event",text:f(e.text),unsent:!1};case m.Deleted:return{kind:"message",text:null,unsent:!0};case m.Audio:case m.Document:case m.Sticker:return{kind:"message",text:null,unsent:!1};default:return f(e.text)||t?.path?{kind:"message",text:f(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=vs[e.type];if(!t||!t.path&&!s||e.type===m.Text||e.type===m.Link)return[];let r=t.path?Be(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?ps(t.path):null,a=e.type===m.Document?f(t.title)??f(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?ws[gs(t.path).toLowerCase()]:void 0)??bs[e.type]??null,bytes:t.bytes,path:r&&be(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,Ze(e.jid)].filter((t)=>!!t))]}async settled(e,t){let s=Date.now()+this.settleMs;while(!0){if(this.open((r)=>r.session(e))?.archived===t)return!0;if(Date.now()>=s)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((r)=>r.session(e));if(!t)return{mode:"unsupported",removed:!1,detail:"WhatsApp no longer has this chat."};if(t.archived)return{mode:"native",removed:!0,detail:"Already archived in WhatsApp."};if(this.controller){let r=await this.controller.archive(this.rowNames(t));if(!r.ok)return{mode:"native",removed:!1,detail:r.detail};let n=await this.settled(e,!0);return{mode:"native",removed:n,detail:n?"Archived in WhatsApp.":"WhatsApp did not record the archive."}}let s=this.url(t);if(s)this.openUrl(s);return{mode:"confirm",removed:!1,detail:s?"WhatsApp is open on the chat: choose Archive chat from its menu to finish.":"Archive it in WhatsApp: WhatsApp cannot open a group by link."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>r.session(e));if(!t?.archived)return;if((await this.controller.unarchive(this.rowNames(t))).ok)await this.settled(e,!1)}async attachment(e){if(!e.path||!be(e.path))return new Response("Not found",{status:404});return new Response(Bun.file(e.path),{headers:{"content-type":e.mimeType??"application/octet-stream"}})}}function xs(e,t){switch(e){case"imessage":return new X(t);case"slack":return new te(t);case"discord":return new V(t);case"whatsapp":return new ie(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}import{createMessenger as Qr,MessagingError as en}from"@stacksjs/orm";async function Hr(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return le(e);case"discord":return oe(e);case"teams":return ue(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Gr(e,t,s){return ce(e,t,s)}async function Jr(e,t,s){return W(e,t,s)}async function zr(e,t){return de(e,t)}function Vr(e){j(e)}function Kr(e){F(e)}function qr(e){Z(e)}export{en as MessagingError,Kr as configureDiscord,Vr as configureSlack,qr as configureTeams,Qr as createMessenger,we as discord,ft as inbox,Hr as send,Jr as sendToDiscord,Gr as sendToSlack,zr as sendToTeams,Ae as slack,_e as teams};
93
+ `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:U(i.date)??0,senderJid:i.senderJid,senderName:f(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var ys="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",bs={[m.Image]:"image/jpeg",[m.Video]:"video/mp4",[m.Audio]:"audio/ogg",[m.Gif]:"video/mp4",[m.Sticker]:"image/webp"},vs={[m.Image]:"Photo",[m.Video]:"Video",[m.Audio]:"Voice message",[m.Document]:"Document",[m.Gif]:"GIF",[m.Sticker]:"Sticker"},ws={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp",".heic":"image/heic",".mp4":"video/mp4",".mov":"video/quicktime",".opus":"audio/ogg",".ogg":"audio/ogg",".m4a":"audio/mp4",".mp3":"audio/mpeg",".pdf":"application/pdf"};function Ss(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function Ze(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function As(e){let t=e?.match(/^FN:(.*)$/m);return f(t?.[1])}class ie{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??Le,this.mediaRoot=e.mediaRoot??(e.databasePath?Be(je(e.databasePath),"Message"):gt),this.controller=e.controller,this.openUrl=e.openUrl??Ss,this.platform=e.platform??fs.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=Be(je(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>be(s))??null}pictures(e){let t=e.profilePicturePaths(),s=new Map;return(r)=>{if(!s.has(r))s.set(r,this.pictureFile(t.get(r))?r:null);return s.get(r)}}async avatar(e){let t=this.open((s)=>this.pictureFile(s.profilePicturePaths().get(e)));return G(t?new Uint8Array(hs(t)):null)}open(e){let t=new We(this.databasePath);try{return e(t)}finally{t.close()}}async status(){if(this.platform!=="darwin")return{connected:!1,detail:"WhatsApp is read from WhatsApp for Mac, so it needs a Mac.",needs:"unsupported-platform"};try{return this.open(()=>{return}),{connected:!0,detail:"Reading WhatsApp on this Mac."}}catch(e){if(e instanceof ye&&e.needsFullDiskAccess&&be(je(this.databasePath)))return{connected:!1,detail:"Grant Full Disk Access in System Settings > Privacy & Security.",needs:"full-disk-access"};return{connected:!1,detail:"Install WhatsApp for Mac and link it to your phone.",needs:"signed-out"}}}archiveSupport(e){if(this.controller)return{mode:"native",detail:ys};return this.url(e)?{mode:"confirm",detail:"Opens the chat in WhatsApp for you to archive it there."}:{mode:"unsupported",detail:"WhatsApp cannot open a group by link, so archive it there yourself."}}url(e){let[t,s]=e.jid.split("@");return s==="s.whatsapp.net"&&t&&/^\d{7,15}$/.test(t)?`whatsapp://send?phone=${t}`:null}async conversations(){return this.open((e)=>{let t=this.pictures(e);return e.sessions().map((s)=>{let r=s.type===ne,n=r?[{id:s.jid,name:s.name,avatar:t(s.jid)}]:e.members(s.pk).map((i)=>({id:i.jid,name:i.name,avatar:t(i.jid)}));return{provider:"whatsapp",id:s.jid,kind:r?"direct":"group",title:r?null:s.name,participants:n,workspace:null,lastMessageAt:s.lastMessageAt,preview:s.lastMessageText,previewFromMe:s.lastMessageFromMe,unread:s.unread,cursor:String(s.lastMessagePk),visible:!s.archived,archive:this.archiveSupport(s),url:this.url(s),avatar:r?null:t(s.jid)}})})}async messages(e,t={}){return this.open((s)=>{let r=s.session(e);if(!r)return[];let n=t.after||!t.limit?s.messages(r.pk,{afterPk:Number(t.after)||0,limit:t.limit}):s.messages(r.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(s),a=new Map(r.type===ne?[]:s.members(r.pk).map((l)=>[l.jid,l.name]));return n.flatMap((l)=>[this.toMessage(r,l,i),...this.reactions(r,l,a,i)])})}toMessage(e,t,s){let r=t.isFromMe?null:e.type===ne?{id:e.jid,name:e.name,avatar:s(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:s(t.senderJid)}:null,{kind:n,text:i,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:n,text:i,fromMe:t.isFromMe,sentAt:t.sentAt,sender:r,targetId:null,reaction:null,reactionRemoved:!1,replyToId:re(se(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,s,r){let n=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Ue(se(t.receipts),7))Ue(se(a),1).forEach((l,o)=>{let d=se(l),c=re(d,3);if(!c)return;let h=re(d,2),p=!h&&$e(d,6)===1n,y=h??(p?null:e.jid),u=Number($e(d,4)??0n);i.push({id:re(d,1)??`${n}:reaction:${o}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:p,sentAt:u>0?u:t.sentAt,sender:y?{id:y,name:s.get(y)??(y===e.jid?e.name:null),avatar:r(y)}:null,targetId:n,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case m.Text:case m.Link:return{kind:"message",text:f(e.text),unsent:!1};case m.Image:case m.Video:case m.Gif:return{kind:"message",text:f(e.text)??f(t?.title),unsent:!1};case m.Contact:{let s=As(t?.vcard??null)??f(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case m.Location:{let s=f(t?.title),r=t?.latitude!=null&&t.longitude!=null&&(t.latitude!==0||t.longitude!==0)?`https://maps.apple.com/?ll=${t.latitude},${t.longitude}`:null;return{kind:"message",text:[s??"Shared a location",r].filter(Boolean).join(" "),unsent:!1}}case m.GroupEvent:case m.Call:return{kind:"event",text:f(e.text),unsent:!1};case m.Deleted:return{kind:"message",text:null,unsent:!0};case m.Audio:case m.Document:case m.Sticker:return{kind:"message",text:null,unsent:!1};default:return f(e.text)||t?.path?{kind:"message",text:f(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=vs[e.type];if(!t||!t.path&&!s||e.type===m.Text||e.type===m.Link)return[];let r=t.path?Be(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?ps(t.path):null,a=e.type===m.Document?f(t.title)??f(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?ws[gs(t.path).toLowerCase()]:void 0)??bs[e.type]??null,bytes:t.bytes,path:r&&be(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,Ze(e.jid)].filter((t)=>!!t))]}async settled(e,t){let s=Date.now()+this.settleMs;while(!0){if(this.open((r)=>r.session(e))?.archived===t)return!0;if(Date.now()>=s)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((r)=>r.session(e));if(!t)return{mode:"unsupported",removed:!1,detail:"WhatsApp no longer has this chat."};if(t.archived)return{mode:"native",removed:!0,detail:"Already archived in WhatsApp."};if(this.controller){let r=await this.controller.archive(this.rowNames(t));if(!r.ok)return{mode:"native",removed:!1,detail:r.detail};let n=await this.settled(e,!0);return{mode:"native",removed:n,detail:n?"Archived in WhatsApp.":"WhatsApp did not record the archive."}}let s=this.url(t);if(s)this.openUrl(s);return{mode:"confirm",removed:!1,detail:s?"WhatsApp is open on the chat: choose Archive chat from its menu to finish.":"Archive it in WhatsApp: WhatsApp cannot open a group by link."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>r.session(e));if(!t?.archived)return;if((await this.controller.unarchive(this.rowNames(t))).ok)await this.settled(e,!1)}async attachment(e){if(!e.path||!be(e.path))return new Response("Not found",{status:404});return new Response(Bun.file(e.path),{headers:{"content-type":e.mimeType??"application/octet-stream"}})}}function xs(e,t){switch(e){case"imessage":return new X(t);case"slack":return new te(t);case"discord":return new V(t);case"whatsapp":return new ie(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}import{createMessenger as Qr,messagingChannel as en,MessagingError as tn}from"@stacksjs/orm";async function Hr(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return le(e);case"discord":return oe(e);case"teams":return ue(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Gr(e,t,s){return ce(e,t,s)}async function Jr(e,t,s){return W(e,t,s)}async function zr(e,t){return de(e,t)}function Vr(e){j(e)}function Kr(e){F(e)}function qr(e){Z(e)}export{tn as MessagingError,Kr as configureDiscord,Vr as configureSlack,qr as configureTeams,Qr as createMessenger,we as discord,ft as inbox,en as messagingChannel,Hr as send,Jr as sendToDiscord,Gr as sendToSlack,zr as sendToTeams,Ae as slack,_e as teams};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/chat",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.121",
5
+ "version": "0.75.122",
6
6
  "description": "Easily interact with chat APIs.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,14 +58,14 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "dependencies": {
61
- "@stacksjs/orm": "0.75.121",
62
- "@stacksjs/config": "0.75.121",
63
- "@stacksjs/logging": "0.75.121"
61
+ "@stacksjs/orm": "0.75.122",
62
+ "@stacksjs/config": "0.75.122",
63
+ "@stacksjs/logging": "0.75.122"
64
64
  },
65
65
  "devDependencies": {
66
- "@stacksjs/cli": "0.75.121",
67
- "@stacksjs/error-handling": "0.75.121",
68
- "@stacksjs/types": "0.75.121",
66
+ "@stacksjs/cli": "0.75.122",
67
+ "@stacksjs/error-handling": "0.75.122",
68
+ "@stacksjs/types": "0.75.122",
69
69
  "better-dx": "^0.2.24"
70
70
  }
71
71
  }