@stacksjs/chat 0.75.52 → 0.75.53

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.
@@ -29,5 +29,6 @@ export declare class DiscordInboxDriver implements InboxDriver {
29
29
  messages(conversationId: string, query?: MessageQuery): Promise<InboxMessage[]>;
30
30
  archive(conversationId: string): Promise<ArchiveOutcome>;
31
31
  unarchive(conversationId: string): Promise<void>;
32
+ avatar(ref: string): Promise<Response>;
32
33
  attachment(attachment: InboxAttachment): Promise<Response>;
33
34
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Profile pictures as HTTP responses. Contacts and WhatsApp store photos
3
+ * without a type, so it is read from the bytes; remote pictures are only
4
+ * fetched from the provider's own image hosts, never an arbitrary URL.
5
+ */
6
+ export declare function imageType(bytes: Uint8Array): string;
7
+ export declare function imageResponse(bytes: Uint8Array | null): Response;
8
+ /** Whether `ref` is an https URL on one of `hosts` (or a subdomain of one). */
9
+ export declare function onHost(ref: string, hosts: string[]): boolean;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * A contact photo column's bytes as the image they stand for. Contacts writes
3
+ * a one-byte tag first: 1 means the JPEG follows inline, 2 means the rest is
4
+ * the UUID of a file in the store's `.AddressBook-v22_SUPPORT/_EXTERNAL_DATA`
5
+ * (iCloud's larger photos), NUL-terminated.
6
+ */
7
+ export declare function contactImage(store: string, data: Uint8Array | null): Uint8Array | null;
1
8
  export declare function initialsOf(name: string): string;
2
9
  /**
3
10
  * Names for handles, read from the Contacts database the way Messages shows
@@ -14,6 +21,7 @@ export declare const DEFAULT_ADDRESS_BOOK_DIR: unknown;
14
21
  export declare interface ContactCard {
15
22
  name: string
16
23
  initials: string
24
+ photo?: { file: string, record: number } | null
17
25
  }
18
26
  export declare class Contacts {
19
27
  static load(root?: string): Contacts;
@@ -21,4 +29,6 @@ export declare class Contacts {
21
29
  set(handle: string, name: string): void;
22
30
  lookup(handle: string): ContactCard | null;
23
31
  nameFor(handle: string): string | null;
32
+ hasPhoto(handle: string): boolean;
33
+ photo(handle: string): Uint8Array | null;
24
34
  }
@@ -35,6 +35,7 @@ export declare class IMessageDriver implements InboxDriver {
35
35
  readonly provider: 'imessage';
36
36
  readonly label: string;
37
37
  constructor(config?: IMessageConfig);
38
+ avatar(ref: string): Promise<Response>;
38
39
  status(): Promise<InboxStatus>;
39
40
  conversations(): Promise<InboxConversation[]>;
40
41
  messages(conversationId: string, query?: MessageQuery): Promise<InboxMessage[]>;
@@ -23,6 +23,7 @@ export declare class SlackInboxDriver implements InboxDriver {
23
23
  readonly provider: 'slack';
24
24
  readonly label: string;
25
25
  constructor(config: SlackInboxConfig);
26
+ avatar(ref: string): Promise<Response>;
26
27
  status(): Promise<InboxStatus>;
27
28
  conversations(): Promise<InboxConversation[]>;
28
29
  messages(conversationId: string, query?: MessageQuery): Promise<InboxMessage[]>;
@@ -5,6 +5,7 @@ export declare interface ArchiveSupport {
5
5
  export declare interface InboxPerson {
6
6
  id: string
7
7
  name: string | null
8
+ avatar?: string | null
8
9
  }
9
10
  export declare interface InboxConversation {
10
11
  provider: InboxProvider
@@ -22,6 +23,7 @@ export declare interface InboxConversation {
22
23
  deleted?: number
23
24
  archive: ArchiveSupport
24
25
  url: string | null
26
+ avatar?: string | null
25
27
  }
26
28
  export declare interface InboxAttachment {
27
29
  id: string
@@ -72,6 +74,7 @@ export declare interface InboxDriver {
72
74
  archive: (conversationId: string) => Promise<ArchiveOutcome>
73
75
  unarchive: (conversationId: string) => Promise<void>
74
76
  attachment: (attachment: InboxAttachment) => Promise<Response>
77
+ avatar?: (ref: string) => Promise<Response>
75
78
  }
76
79
  /**
77
80
  * The inbox side of `@stacksjs/chat`: reading conversations out of the apps
@@ -82,6 +82,7 @@ export declare class WhatsAppDb {
82
82
  close(): void;
83
83
  sessions(): SessionRow[];
84
84
  session(jid: string): SessionRow | undefined;
85
+ profilePicturePaths(): Map<string, string>;
85
86
  members(sessionPk: number): MemberRow[];
86
87
  messages(sessionPk: number, options?: { afterPk?: number, limit?: number, newestFirst?: boolean }): MessageRow[];
87
88
  }
@@ -34,6 +34,7 @@ export declare class WhatsAppDriver implements InboxDriver {
34
34
  readonly provider: 'whatsapp';
35
35
  readonly label: string;
36
36
  constructor(config?: WhatsAppConfig);
37
+ avatar(ref: string): Promise<Response>;
37
38
  status(): Promise<InboxStatus>;
38
39
  conversations(): Promise<InboxConversation[]>;
39
40
  messages(conversationId: string, query?: MessageQuery): Promise<InboxMessage[]>;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var Te=Object.defineProperty;var Re=(e)=>e;function Ie(e,t){this[e]=Re.bind(null,t)}var I=(e,t)=>{for(var s in t)Te(e,s,{get:t[s],enumerable:!0,configurable:!0,set:Ie.bind(t,s)})};var z={};I(z,{DiscordDriver:()=>v,Driver:()=>v,configure:()=>y,default:()=>Oe,driver:()=>Pe,send:()=>D,sendEmbed:()=>ue,sendWebhook:()=>A});import{log as de}from"@stacksjs/logging";import{log as De}from"@stacksjs/logging";class h{config;constructor(e){this.config={maxRetries:e?.maxRetries||3,retryTimeout:e?.retryTimeout||1000,...e}}configure(e){this.config={...this.config,...e}}validateMessage(e){if(!e.to)throw Error("Message recipient is required");if(!e.content&&!e.template)throw Error("Either message content or template is required");return!0}async handleError(e,t){let s=e instanceof Error?e:Error(String(e));De.error(`[${this.name}] Message sending failed`,{error:s.message,stack:s.stack?.replace(/https?:\/\/[^\s)\]"']+/g,"[REDACTED-URL]"),to:t.to,subject:t.subject});let r={message:`Message sending failed: ${s.message}`,success:!1,provider:this.name};if(t.onError){let n=t.onError(s),a=n instanceof Promise?await n:n;r={...r,...a,success:!1,provider:this.name}}return r}async handleSuccess(e,t){let s={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let r=e.handle(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let r=e.onSuccess(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}}catch(r){return this.handleError(r,e)}return s}}var g={};function y(e){if(e.webhookUrl){let t;try{t=new URL(e.webhookUrl)}catch{throw Error(`[chat/discord] webhookUrl is not a valid URL: ${e.webhookUrl}`)}if(t.protocol!=="https:")throw Error("[chat/discord] webhookUrl must use https://");if(!/(?:^|\.)discord(?:app)?\.com$/i.test(t.hostname))throw Error(`[chat/discord] webhookUrl host "${t.hostname}" is not a Discord-owned domain.`)}g={...g,...e}}class v extends h{name="discord";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)y(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};de.info("Sending message via Discord...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.id)}catch(r){if(s<this.config.maxRetries)return de.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(g.webhookUrl)return this.sendViaWebhook(e);if(g.botToken)return this.sendViaBotToken(e);throw Error("Discord not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let t={content:e.content,username:e.from?.name};if(e.subject||e.template)t.embeds=[this.buildEmbed(e)];let s=await fetch(g.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){let n=await s.text();throw Error(`Discord webhook failed: ${s.status} - ${n}`)}if(s.status===204)return{};return{id:(await s.json()).id}}async sendViaBotToken(e){let t=e.to,s={content:e.content};if(e.subject||e.template)s.embeds=[this.buildEmbed(e)];let r=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${g.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let a=await r.json();throw Error(`Discord API failed: ${r.status} - ${a.message}`)}return{id:(await r.json()).id}}buildEmbed(e){let t={};if(e.subject)t.title=e.subject;if(e.content)t.description=e.content;return t.color=5793266,t.timestamp=new Date().toISOString(),t}}async function D(e,t){return new v().send(e,t)}async function A(e,t,s){try{let r={content:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let a=await n.text();return{success:!1,provider:"discord",message:`Webhook failed: ${n.status} - ${a}`}}return{success:!0,provider:"discord",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"discord",message:(r instanceof Error?r:Error(String(r))).message}}}async function ue(e,t,s){return A(e,"",{...s,embeds:[t]})}var Pe=new v,Oe={send:D,sendWebhook:A,sendEmbed:ue,configure:y,DiscordDriver:v};var K={};I(K,{Driver:()=>M,SlackDriver:()=>M,configure:()=>x,default:()=>Le,driver:()=>Ne,send:()=>P,sendWebhook:()=>O});import{log as me}from"@stacksjs/logging";var f={};function x(e){if(e.webhookUrl){let t;try{t=new URL(e.webhookUrl)}catch{throw Error(`[chat/slack] webhookUrl is not a valid URL: ${e.webhookUrl}`)}if(t.protocol!=="https:")throw Error("[chat/slack] webhookUrl must use https:// - Slack does not accept plain HTTP webhooks.");if(!/(?:^|\.)slack\.com$/i.test(t.hostname))throw Error(`[chat/slack] webhookUrl host "${t.hostname}" is not a Slack-owned domain.`)}f={...f,...e}}class M extends h{name="slack";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)x(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};me.info("Sending message via Slack...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.ts)}catch(r){if(s<this.config.maxRetries)return me.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(f.webhookUrl)return this.sendViaWebhook(e);if(f.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let s=e.content||e.subject||"",n={text:s.length>40000?`${s.slice(0,39986)}\u2026 [truncated]`:s,username:e.from?.name,mrkdwn:!0};if(e.template)n.blocks=this.buildBlocks(e);let a=await fetch(f.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok){let i=await a.text();throw Error(`Slack webhook failed: ${a.status} - ${i}`)}return{}}async sendViaBotToken(e){let t={channel:e.to,text:e.content||e.subject,mrkdwn:!0};if(e.template)t.blocks=this.buildBlocks(e);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${f.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok)throw Error(`Slack API failed: ${s.status}`);let r=await s.json();if(!r.ok)throw Error(`Slack API error: ${r.error}`);return{ts:r.ts}}buildBlocks(e){let t=[];if(e.subject)t.push({type:"header",text:{type:"plain_text",text:e.subject,emoji:!0}});if(e.content)t.push({type:"section",text:{type:"mrkdwn",text:e.content}});return t}}async function P(e,t){return new M().send(e,t)}async function O(e,t,s){try{let r={text:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let a=await n.text();return{success:!1,provider:"slack",message:`Webhook failed: ${n.status} - ${a}`}}return{success:!0,provider:"slack",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"slack",message:(r instanceof Error?r:Error(String(r))).message}}}var Ne=new M,Le={send:P,sendWebhook:O,configure:x,SlackDriver:M};var Y={};I(Y,{Driver:()=>C,TeamsDriver:()=>C,configure:()=>S,default:()=>$e,driver:()=>Fe,send:()=>N,sendCard:()=>he,sendWebhook:()=>L});import{log as pe}from"@stacksjs/logging";var V={};function S(e){V={...V,...e}}class C extends h{name="teams";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)S(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};pe.info("Sending message via Microsoft Teams...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(r){if(s<this.config.maxRetries)return pe.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){let r=V.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!r||!r.includes("webhook.office.com"))throw Error("Teams webhook URL not configured or invalid");let n=this.buildPayload(e),a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok){let i=await a.text();throw Error(`Teams webhook failed: ${a.status} - ${i}`)}}buildPayload(e){if(!e.subject&&!e.template)return{type:"message",text:e.content||""};let t=this.buildAdaptiveCard(e);return{type:"message",summary:e.subject||e.content?.substring(0,50)||"Notification",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:t}]}}buildAdaptiveCard(e){let t=[];if(e.subject)t.push({type:"TextBlock",text:e.subject,size:"Large",weight:"Bolder",wrap:!0});if(e.content)t.push({type:"TextBlock",text:e.content,wrap:!0});return t.push({type:"TextBlock",text:new Date().toLocaleString(),size:"Small",color:"Dark",wrap:!0}),{type:"AdaptiveCard",version:"1.4",$schema:"http://adaptivecards.io/schemas/adaptive-card.json",body:t}}}async function N(e,t){return new C().send(e,t)}async function L(e,t){try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",text:t})});if(!r.ok){let n=await r.text();return{success:!1,provider:"teams",message:`Webhook failed: ${r.status} - ${n}`}}return{success:!0,provider:"teams",message:"Message sent successfully"}}catch(s){return{success:!1,provider:"teams",message:(s instanceof Error?s:Error(String(s))).message}}}async function he(e,t,s){try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",summary:s||"Notification",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:t}]})});if(!n.ok){let a=await n.text();return{success:!1,provider:"teams",message:`Webhook failed: ${n.status} - ${a}`}}return{success:!0,provider:"teams",message:"Card sent successfully"}}catch(r){return{success:!1,provider:"teams",message:(r instanceof Error?r:Error(String(r))).message}}}var Fe=new C,$e={send:N,sendWebhook:L,sendCard:he,configure:S,TeamsDriver:C};var Ee={};I(Ee,{DiscordInboxDriver:()=>k,IMessageDriver:()=>w,SlackApiError:()=>H,SlackInboxDriver:()=>E,WhatsAppDriver:()=>T,createInboxDriver:()=>yt,decodeAttributedBody:()=>j,formatHandle:()=>F,formatJid:()=>ce,normalizeHandle:()=>m,snowflakeTime:()=>q});var je=new Set([0,5]),X=new Set([10,11,12]);function q(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}class k{provider="discord";label="Discord";token;userId;fetch;apiBase;concurrency;guilds=new Map;channels=new Map;constructor(e){this.token=e.token,this.userId=e.userId,this.fetch=e.fetch??((t,s)=>fetch(t,s)),this.apiBase=(e.apiBase??"https://discord.com/api/v10").replace(/\/$/,""),this.concurrency=e.concurrency??3}async api(e,t={}){for(let s=0;;s++){let r=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{authorization:`Bot ${this.token}`,"content-type":"application/json",...t.headers??{}}});if(r.status===429&&s<3){let n=await r.json().catch(()=>({}));await Bun.sleep(Math.min(30,n.retry_after??1)*1000);continue}if(!r.ok){let n=await r.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${n.message??r.status}`)}return r.status===204?void 0:await r.json()}}async status(){if(!this.token)return{connected:!1,detail:"Add a Discord bot token.",needs:"token"};try{return{connected:!0,detail:`Reading the servers ${(await this.api("/users/@me")).username} is in.`}}catch(e){return{connected:!1,detail:e instanceof Error?e.message:String(e),needs:"token"}}}archiveSupport(e){if(X.has(e.type))return{mode:"native",detail:"Archives the thread in Discord. It reopens when someone posts."};return{mode:"unsupported",detail:"Discord cannot hide a channel for one person, so it stays in Discord."}}async conversations(){let e=await this.api("/users/@me/guilds"),t=[];for(let r of e){this.guilds.set(r.id,r.name);let[n,a]=await Promise.all([this.api(`/guilds/${r.id}/channels`).catch(()=>[]),this.api(`/guilds/${r.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...n.filter((i)=>je.has(i.type)).map((i)=>({...i,guild_id:r.id}))),t.push(...a.threads.map((i)=>({...i,guild_id:i.guild_id??r.id})))}let s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((a)=>this.describe(a))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e){this.channels.set(e.id,e);let t=e.last_message_id?(await this.api(`/channels/${e.id}/messages?limit=1`).catch(()=>[]))[0]:void 0,s=X.has(e.type);return{provider:"discord",id:e.id,kind:s?"thread":"channel",title:s?e.name??"Thread":`#${e.name}`,participants:[],workspace:e.guild_id?this.guilds.get(e.guild_id)??null:null,lastMessageAt:t?Date.parse(t.timestamp):q(e.last_message_id),preview:t?t.content||(t.attachments?.length?"Attachment":null):null,previewFromMe:!!t&&t.author.id===this.userId,unread:0,cursor:e.last_message_id??"0",visible:!e.thread_metadata?.archived,archive:this.archiveSupport(e),url:`https://discord.com/channels/${e.guild_id??"@me"}/${e.id}`}}async messages(e,t={}){let s=t.limit??1000,r=[],n=(i,o)=>BigInt(i.id)<BigInt(o.id)?-1:1;if(t.after){let i=t.after;while(r.length<s){let o=await this.api(`/channels/${e}/messages?limit=100&after=${i}`);if(o.length===0)break;if(o.sort(n),r.push(...o),i=o[o.length-1].id,o.length<100)break}}else{let i="";while(r.length<s){let o=await this.api(`/channels/${e}/messages?limit=${Math.min(100,s-r.length)}${i?`&before=${i}`:""}`);if(o.length===0)break;if(o.sort(n),r.unshift(...o),i=o[0].id,o.length<100)break}}let a=[];for(let i of r.slice(0,s)){let o=i.author.id===this.userId;a.push({id:i.id,conversationId:e,kind:[0,19,20,21].includes(i.type)?"message":"event",text:i.content||null,fromMe:o,sentAt:Date.parse(i.timestamp),sender:o?null:{id:i.author.id,name:i.author.global_name||i.author.username},targetId:null,reaction:null,reactionRemoved:!1,replyToId:i.message_reference?.message_id??null,editedAt:i.edited_timestamp?Date.parse(i.edited_timestamp):null,unsent:!1,attachments:(i.attachments??[]).map((l)=>({id:l.id,name:l.filename,mimeType:l.content_type??null,bytes:l.size,path:null,url:l.url})),cursor:i.id});for(let l of i.reactions??[])a.push({id:`${i.id}:${l.emoji.id??l.emoji.name}`,conversationId:e,kind:"reaction",text:l.count>1?String(l.count):null,fromMe:!!l.me,sentAt:Date.parse(i.timestamp),sender:null,targetId:i.id,reaction:l.emoji.name??"\u2022",reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:i.id})}return a}async channel(e){let t=this.channels.get(e);if(t)return t;let s=await this.api(`/channels/${e}`);return this.channels.set(e,s),s}async archive(e){let t=await this.channel(e),s=this.archiveSupport(t);if(s.mode!=="native")return{mode:s.mode,removed:!1,detail:s.detail};return await this.api(`/channels/${e}`,{method:"PATCH",body:JSON.stringify({archived:!0})}),this.channels.set(e,{...t,thread_metadata:{archived:!0}}),{mode:"native",removed:!0,detail:s.detail}}async unarchive(e){let t=await this.channel(e);if(!X.has(t.type))return;await this.api(`/channels/${e}`,{method:"PATCH",body:JSON.stringify({archived:!1})}),this.channels.set(e,{...t,thread_metadata:{archived:!1}})}async attachment(e){return e.url?this.fetch(e.url):new Response("Not found",{status:404})}}import{existsSync as ne}from"fs";import{dirname as et}from"path";import tt from"process";import{Database as He}from"bun:sqlite";import{homedir as ve}from"os";import{join as Ae}from"path";function m(e){let t=e.trim();if(t.includes("@"))return t.toLowerCase();let s=t.replace(/[^\d+]/g,"");if(!/^\+?\d{7,15}$/.test(s))return t;if(s.startsWith("+"))return s;if(s.length===10)return`+1${s}`;return`+${s}`}function ge(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function F(e){let t=e.match(/^\+1(\d{3})(\d{3})(\d{4})$/);if(t)return`+1 (${t[1]}) ${t[2]}-${t[3]}`;return e}var fe=new TextEncoder,We=new TextDecoder("utf-8",{fatal:!1}),Ue=[fe.encode("NSString"),fe.encode("NSMutableString")];function Be(e,t,s=0){e:for(let r=s;r<=e.length-t.length;r++){for(let n=0;n<t.length;n++)if(e[r+n]!==t[n])continue e;return r}return-1}function Ze(e,t){let s=e[t];if(s===void 0)return null;let r=new DataView(e.buffer,e.byteOffset,e.byteLength);if(s===129){if(t+3>e.length)return null;return{length:r.getUint16(t+1,!0),start:t+3}}if(s===130){if(t+5>e.length)return null;return{length:r.getUint32(t+1,!0),start:t+5}}return{length:s,start:t+1}}function j(e){if(!e||e.length===0)return null;for(let t of Ue){let s=Be(e,t);if(s===-1)continue;let r=s+t.length,n=Math.min(e.length,r+16);for(let a=r;a<n;a++){if(e[a]!==43)continue;let i=Ze(e,a+1);if(!i||i.start+i.length>e.length)continue;return We.decode(e.subarray(i.start,i.start+i.length))}}return null}var Q=Ae(ve(),"Library","Messages","chat.db"),xe=978307200,Ge=45,W=43;function b(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+xe)*1000)}function Je(e){return Math.round((e/1000-xe)*1e9)}function be(e,t){let r=(e?.trim()?e:j(t))?.replace(/\uFFFC/g,"").trim();return r?r:null}class U extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t),r=/authorization denied|not permitted|unable to open/i.test(s);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=r}}var ze=(e)=>e>=2000&&e<4000;function ye(e){if(!e)return null;return e.startsWith("~/")?Ae(ve(),e.slice(2)):e}function Ke(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class ee{path;db;messageColumns;hasRecoverable;constructor(e=Q){this.path=e;try{this.db=new He(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new U(e,t)}this.messageColumns=new Set(this.db.query("PRAGMA table_info(message)").all().map((t)=>t.name)),this.hasRecoverable=!!this.db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'chat_recoverable_message_join'").get()}close(){this.db.close()}latestRowId(){return this.db.query("SELECT MAX(ROWID) AS id FROM message").get().id??0}column(e,t="NULL"){return this.messageColumns.has(e)?`m.${e}`:t}chats(){let e=this.hasRecoverable?"(SELECT COUNT(*) FROM chat_recoverable_message_join r WHERE r.chat_id = c.ROWID)":"0",t=this.db.query(`
2
+ var Le=Object.defineProperty;var Fe=(e)=>e;function $e(e,t){this[e]=Fe.bind(null,t)}var P=(e,t)=>{for(var s in t)Le(e,s,{get:t[s],enumerable:!0,configurable:!0,set:$e.bind(t,s)})};var Q={};P(Q,{DiscordDriver:()=>x,Driver:()=>x,configure:()=>A,default:()=>We,driver:()=>Ue,send:()=>O,sendEmbed:()=>ye,sendWebhook:()=>M});import{log as be}from"@stacksjs/logging";import{log as je}from"@stacksjs/logging";class h{config;constructor(e){this.config={maxRetries:e?.maxRetries||3,retryTimeout:e?.retryTimeout||1000,...e}}configure(e){this.config={...this.config,...e}}validateMessage(e){if(!e.to)throw Error("Message recipient is required");if(!e.content&&!e.template)throw Error("Either message content or template is required");return!0}async handleError(e,t){let s=e instanceof Error?e:Error(String(e));je.error(`[${this.name}] Message sending failed`,{error:s.message,stack:s.stack?.replace(/https?:\/\/[^\s)\]"']+/g,"[REDACTED-URL]"),to:t.to,subject:t.subject});let r={message:`Message sending failed: ${s.message}`,success:!1,provider:this.name};if(t.onError){let n=t.onError(s),i=n instanceof Promise?await n:n;r={...r,...i,success:!1,provider:this.name}}return r}async handleSuccess(e,t){let s={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let r=e.handle(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let r=e.onSuccess(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}}catch(r){return this.handleError(r,e)}return s}}var f={};function A(e){if(e.webhookUrl){let t;try{t=new URL(e.webhookUrl)}catch{throw Error(`[chat/discord] webhookUrl is not a valid URL: ${e.webhookUrl}`)}if(t.protocol!=="https:")throw Error("[chat/discord] webhookUrl must use https://");if(!/(?:^|\.)discord(?:app)?\.com$/i.test(t.hostname))throw Error(`[chat/discord] webhookUrl host "${t.hostname}" is not a Discord-owned domain.`)}f={...f,...e}}class x extends h{name="discord";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)A(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};be.info("Sending message via Discord...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.id)}catch(r){if(s<this.config.maxRetries)return be.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(f.webhookUrl)return this.sendViaWebhook(e);if(f.botToken)return this.sendViaBotToken(e);throw Error("Discord not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let t={content:e.content,username:e.from?.name};if(e.subject||e.template)t.embeds=[this.buildEmbed(e)];let s=await fetch(f.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){let n=await s.text();throw Error(`Discord webhook failed: ${s.status} - ${n}`)}if(s.status===204)return{};return{id:(await s.json()).id}}async sendViaBotToken(e){let t=e.to,s={content:e.content};if(e.subject||e.template)s.embeds=[this.buildEmbed(e)];let r=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${f.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let i=await r.json();throw Error(`Discord API failed: ${r.status} - ${i.message}`)}return{id:(await r.json()).id}}buildEmbed(e){let t={};if(e.subject)t.title=e.subject;if(e.content)t.description=e.content;return t.color=5793266,t.timestamp=new Date().toISOString(),t}}async function O(e,t){return new x().send(e,t)}async function M(e,t,s){try{let r={content:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"discord",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"discord",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"discord",message:(r instanceof Error?r:Error(String(r))).message}}}async function ye(e,t,s){return M(e,"",{...s,embeds:[t]})}var Ue=new x,We={send:O,sendWebhook:M,sendEmbed:ye,configure:A,DiscordDriver:x};var ee={};P(ee,{Driver:()=>C,SlackDriver:()=>C,configure:()=>S,default:()=>Ze,driver:()=>Be,send:()=>N,sendWebhook:()=>L});import{log as ve}from"@stacksjs/logging";var b={};function S(e){if(e.webhookUrl){let t;try{t=new URL(e.webhookUrl)}catch{throw Error(`[chat/slack] webhookUrl is not a valid URL: ${e.webhookUrl}`)}if(t.protocol!=="https:")throw Error("[chat/slack] webhookUrl must use https:// - Slack does not accept plain HTTP webhooks.");if(!/(?:^|\.)slack\.com$/i.test(t.hostname))throw Error(`[chat/slack] webhookUrl host "${t.hostname}" is not a Slack-owned domain.`)}b={...b,...e}}class C extends h{name="slack";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)S(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};ve.info("Sending message via Slack...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.ts)}catch(r){if(s<this.config.maxRetries)return ve.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(b.webhookUrl)return this.sendViaWebhook(e);if(b.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let s=e.content||e.subject||"",n={text:s.length>40000?`${s.slice(0,39986)}\u2026 [truncated]`:s,username:e.from?.name,mrkdwn:!0};if(e.template)n.blocks=this.buildBlocks(e);let i=await fetch(b.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){let a=await i.text();throw Error(`Slack webhook failed: ${i.status} - ${a}`)}return{}}async sendViaBotToken(e){let t={channel:e.to,text:e.content||e.subject,mrkdwn:!0};if(e.template)t.blocks=this.buildBlocks(e);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${b.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok)throw Error(`Slack API failed: ${s.status}`);let r=await s.json();if(!r.ok)throw Error(`Slack API error: ${r.error}`);return{ts:r.ts}}buildBlocks(e){let t=[];if(e.subject)t.push({type:"header",text:{type:"plain_text",text:e.subject,emoji:!0}});if(e.content)t.push({type:"section",text:{type:"mrkdwn",text:e.content}});return t}}async function N(e,t){return new C().send(e,t)}async function L(e,t,s){try{let r={text:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"slack",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"slack",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"slack",message:(r instanceof Error?r:Error(String(r))).message}}}var Be=new C,Ze={send:N,sendWebhook:L,configure:S,SlackDriver:C};var se={};P(se,{Driver:()=>_,TeamsDriver:()=>_,configure:()=>k,default:()=>Ge,driver:()=>He,send:()=>F,sendCard:()=>xe,sendWebhook:()=>j});import{log as Ae}from"@stacksjs/logging";var te={};function k(e){te={...te,...e}}class _ extends h{name="teams";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)k(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Ae.info("Sending message via Microsoft Teams...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(r){if(s<this.config.maxRetries)return Ae.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){let r=te.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!r||!r.includes("webhook.office.com"))throw Error("Teams webhook URL not configured or invalid");let n=this.buildPayload(e),i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){let a=await i.text();throw Error(`Teams webhook failed: ${i.status} - ${a}`)}}buildPayload(e){if(!e.subject&&!e.template)return{type:"message",text:e.content||""};let t=this.buildAdaptiveCard(e);return{type:"message",summary:e.subject||e.content?.substring(0,50)||"Notification",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:t}]}}buildAdaptiveCard(e){let t=[];if(e.subject)t.push({type:"TextBlock",text:e.subject,size:"Large",weight:"Bolder",wrap:!0});if(e.content)t.push({type:"TextBlock",text:e.content,wrap:!0});return t.push({type:"TextBlock",text:new Date().toLocaleString(),size:"Small",color:"Dark",wrap:!0}),{type:"AdaptiveCard",version:"1.4",$schema:"http://adaptivecards.io/schemas/adaptive-card.json",body:t}}}async function F(e,t){return new _().send(e,t)}async function j(e,t){try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",text:t})});if(!r.ok){let n=await r.text();return{success:!1,provider:"teams",message:`Webhook failed: ${r.status} - ${n}`}}return{success:!0,provider:"teams",message:"Message sent successfully"}}catch(s){return{success:!1,provider:"teams",message:(s instanceof Error?s:Error(String(s))).message}}}async function xe(e,t,s){try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",summary:s||"Notification",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:t}]})});if(!n.ok){let i=await n.text();return{success:!1,provider:"teams",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"teams",message:"Card sent successfully"}}catch(r){return{success:!1,provider:"teams",message:(r instanceof Error?r:Error(String(r))).message}}}var He=new _,Ge={send:F,sendWebhook:j,sendCard:xe,configure:k,TeamsDriver:_};var Ne={};P(Ne,{DiscordInboxDriver:()=>w,IMessageDriver:()=>R,SlackApiError:()=>z,SlackInboxDriver:()=>I,WhatsAppDriver:()=>D,createInboxDriver:()=>Tt,decodeAttributedBody:()=>Z,formatHandle:()=>B,formatJid:()=>fe,normalizeHandle:()=>p,snowflakeTime:()=>ne});function Je(e){if(e[0]===255&&e[1]===216)return"image/jpeg";if(e[0]===137&&e[1]===80&&e[2]===78&&e[3]===71)return"image/png";if(e[0]===71&&e[1]===73&&e[2]===70)return"image/gif";if(e[8]===87&&e[9]===69&&e[10]===66&&e[11]===80)return"image/webp";if(String.fromCharCode(e[4]??0,e[5]??0,e[6]??0,e[7]??0)==="ftyp")return"image/heic";return"application/octet-stream"}function U(e){if(!e||e.length===0)return new Response("Not found",{status:404});return new Response(Uint8Array.from(e),{headers:{"content-type":Je(e)}})}function W(e,t){try{let s=new URL(e);return s.protocol==="https:"&&t.some((r)=>s.hostname===r||s.hostname.endsWith(`.${r}`))}catch{return!1}}var ze=new Set([0,5]),re=new Set([10,11,12]);function ne(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}class w{provider="discord";label="Discord";token;userId;fetch;apiBase;concurrency;guilds=new Map;icons=new Map;channels=new Map;constructor(e){this.token=e.token,this.userId=e.userId,this.fetch=e.fetch??((t,s)=>fetch(t,s)),this.apiBase=(e.apiBase??"https://discord.com/api/v10").replace(/\/$/,""),this.concurrency=e.concurrency??3}async api(e,t={}){for(let s=0;;s++){let r=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{authorization:`Bot ${this.token}`,"content-type":"application/json",...t.headers??{}}});if(r.status===429&&s<3){let n=await r.json().catch(()=>({}));await Bun.sleep(Math.min(30,n.retry_after??1)*1000);continue}if(!r.ok){let n=await r.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${n.message??r.status}`)}return r.status===204?void 0:await r.json()}}async status(){if(!this.token)return{connected:!1,detail:"Add a Discord bot token.",needs:"token"};try{return{connected:!0,detail:`Reading the servers ${(await this.api("/users/@me")).username} is in.`}}catch(e){return{connected:!1,detail:e instanceof Error?e.message:String(e),needs:"token"}}}archiveSupport(e){if(re.has(e.type))return{mode:"native",detail:"Archives the thread in Discord. It reopens when someone posts."};return{mode:"unsupported",detail:"Discord cannot hide a channel for one person, so it stays in Discord."}}async conversations(){let e=await this.api("/users/@me/guilds"),t=[];for(let r of e){this.guilds.set(r.id,r.name),this.icons.set(r.id,r.icon?`https://cdn.discordapp.com/icons/${r.id}/${r.icon}.png?size=128`:null);let[n,i]=await Promise.all([this.api(`/guilds/${r.id}/channels`).catch(()=>[]),this.api(`/guilds/${r.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...n.filter((a)=>ze.has(a.type)).map((a)=>({...a,guild_id:r.id}))),t.push(...i.threads.map((a)=>({...a,guild_id:a.guild_id??r.id})))}let s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e){this.channels.set(e.id,e);let t=e.last_message_id?(await this.api(`/channels/${e.id}/messages?limit=1`).catch(()=>[]))[0]:void 0,s=re.has(e.type);return{provider:"discord",id:e.id,kind:s?"thread":"channel",title:s?e.name??"Thread":`#${e.name}`,participants:[],workspace:e.guild_id?this.guilds.get(e.guild_id)??null:null,avatar:e.guild_id?this.icons.get(e.guild_id)??null:null,lastMessageAt:t?Date.parse(t.timestamp):ne(e.last_message_id),preview:t?t.content||(t.attachments?.length?"Attachment":null):null,previewFromMe:!!t&&t.author.id===this.userId,unread:0,cursor:e.last_message_id??"0",visible:!e.thread_metadata?.archived,archive:this.archiveSupport(e),url:`https://discord.com/channels/${e.guild_id??"@me"}/${e.id}`}}async messages(e,t={}){let s=t.limit??1000,r=[],n=(a,o)=>BigInt(a.id)<BigInt(o.id)?-1:1;if(t.after){let a=t.after;while(r.length<s){let o=await this.api(`/channels/${e}/messages?limit=100&after=${a}`);if(o.length===0)break;if(o.sort(n),r.push(...o),a=o[o.length-1].id,o.length<100)break}}else{let a="";while(r.length<s){let o=await this.api(`/channels/${e}/messages?limit=${Math.min(100,s-r.length)}${a?`&before=${a}`:""}`);if(o.length===0)break;if(o.sort(n),r.unshift(...o),a=o[0].id,o.length<100)break}}let i=[];for(let a of r.slice(0,s)){let o=a.author.id===this.userId;i.push({id:a.id,conversationId:e,kind:[0,19,20,21].includes(a.type)?"message":"event",text:a.content||null,fromMe:o,sentAt:Date.parse(a.timestamp),sender:o?null:{id:a.author.id,name:a.author.global_name||a.author.username,avatar:a.author.avatar?`https://cdn.discordapp.com/avatars/${a.author.id}/${a.author.avatar}.png?size=128`:null},targetId:null,reaction:null,reactionRemoved:!1,replyToId:a.message_reference?.message_id??null,editedAt:a.edited_timestamp?Date.parse(a.edited_timestamp):null,unsent:!1,attachments:(a.attachments??[]).map((l)=>({id:l.id,name:l.filename,mimeType:l.content_type??null,bytes:l.size,path:null,url:l.url})),cursor:a.id});for(let l of a.reactions??[])i.push({id:`${a.id}:${l.emoji.id??l.emoji.name}`,conversationId:e,kind:"reaction",text:l.count>1?String(l.count):null,fromMe:!!l.me,sentAt:Date.parse(a.timestamp),sender:null,targetId:a.id,reaction:l.emoji.name??"\u2022",reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:a.id})}return i}async channel(e){let t=this.channels.get(e);if(t)return t;let s=await this.api(`/channels/${e}`);return this.channels.set(e,s),s}async archive(e){let t=await this.channel(e),s=this.archiveSupport(t);if(s.mode!=="native")return{mode:s.mode,removed:!1,detail:s.detail};return await this.api(`/channels/${e}`,{method:"PATCH",body:JSON.stringify({archived:!0})}),this.channels.set(e,{...t,thread_metadata:{archived:!0}}),{mode:"native",removed:!0,detail:s.detail}}async unarchive(e){let t=await this.channel(e);if(!re.has(t.type))return;await this.api(`/channels/${e}`,{method:"PATCH",body:JSON.stringify({archived:!1})}),this.channels.set(e,{...t,thread_metadata:{archived:!1}})}async avatar(e){if(!W(e,["cdn.discordapp.com"]))return new Response("Not found",{status:404});return this.fetch(e)}async attachment(e){return e.url?this.fetch(e.url):new Response("Not found",{status:404})}}import{existsSync as ue}from"fs";import{dirname as ct}from"path";import ut from"process";import{Database as Ye}from"bun:sqlite";import{homedir as _e}from"os";import{join as we}from"path";function p(e){let t=e.trim();if(t.includes("@"))return t.toLowerCase();let s=t.replace(/[^\d+]/g,"");if(!/^\+?\d{7,15}$/.test(s))return t;if(s.startsWith("+"))return s;if(s.length===10)return`+1${s}`;return`+${s}`}function Me(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function B(e){let t=e.match(/^\+1(\d{3})(\d{3})(\d{4})$/);if(t)return`+1 (${t[1]}) ${t[2]}-${t[3]}`;return e}var Se=new TextEncoder,Ke=new TextDecoder("utf-8",{fatal:!1}),Ve=[Se.encode("NSString"),Se.encode("NSMutableString")];function qe(e,t,s=0){e:for(let r=s;r<=e.length-t.length;r++){for(let n=0;n<t.length;n++)if(e[r+n]!==t[n])continue e;return r}return-1}function Xe(e,t){let s=e[t];if(s===void 0)return null;let r=new DataView(e.buffer,e.byteOffset,e.byteLength);if(s===129){if(t+3>e.length)return null;return{length:r.getUint16(t+1,!0),start:t+3}}if(s===130){if(t+5>e.length)return null;return{length:r.getUint32(t+1,!0),start:t+5}}return{length:s,start:t+1}}function Z(e){if(!e||e.length===0)return null;for(let t of Ve){let s=qe(e,t);if(s===-1)continue;let r=s+t.length,n=Math.min(e.length,r+16);for(let i=r;i<n;i++){if(e[i]!==43)continue;let a=Xe(e,i+1);if(!a||a.start+a.length>e.length)continue;return Ke.decode(e.subarray(a.start,a.start+a.length))}}return null}var ie=we(_e(),"Library","Messages","chat.db"),Ee=978307200,Qe=45,H=43;function y(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+Ee)*1000)}function et(e){return Math.round((e/1000-Ee)*1e9)}function Ce(e,t){let r=(e?.trim()?e:Z(t))?.replace(/\uFFFC/g,"").trim();return r?r:null}class G extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t),r=/authorization denied|not permitted|unable to open/i.test(s);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=r}}var tt=(e)=>e>=2000&&e<4000;function ke(e){if(!e)return null;return e.startsWith("~/")?we(_e(),e.slice(2)):e}function st(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class ae{path;db;messageColumns;hasRecoverable;constructor(e=ie){this.path=e;try{this.db=new Ye(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new G(e,t)}this.messageColumns=new Set(this.db.query("PRAGMA table_info(message)").all().map((t)=>t.name)),this.hasRecoverable=!!this.db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'chat_recoverable_message_join'").get()}close(){this.db.close()}latestRowId(){return this.db.query("SELECT MAX(ROWID) AS id FROM message").get().id??0}column(e,t="NULL"){return this.messageColumns.has(e)?`m.${e}`:t}chats(){let e=this.hasRecoverable?"(SELECT COUNT(*) FROM chat_recoverable_message_join r WHERE r.chat_id = c.ROWID)":"0",t=this.db.query(`
3
3
  SELECT
4
4
  c.ROWID AS rowid, c.guid, c.chat_identifier, c.style, c.service_name, c.display_name, c.group_id,
5
5
  (SELECT COUNT(*) FROM chat_message_join j WHERE j.chat_id = c.ROWID) AS message_count,
@@ -9,9 +9,9 @@ var Te=Object.defineProperty;var Re=(e)=>e;function Ie(e,t){this[e]=Re.bind(null
9
9
  AND (m.associated_message_type IS NULL OR m.associated_message_type = 0)) AS unread_count,
10
10
  (SELECT MAX(j.message_id) FROM chat_message_join j WHERE j.chat_id = c.ROWID) AS last_rowid
11
11
  FROM chat c
12
- `).all(),s=this.participantsByChat(),r=this.db.query("SELECT m.text, m.attributedBody, m.date, m.is_from_me FROM message m WHERE m.ROWID = ?");return t.map((a)=>{let i=a.last_rowid?r.get(a.last_rowid):null;return{rowid:a.rowid,guid:a.guid,identifier:a.chat_identifier?m(a.chat_identifier):a.guid,style:a.style??Ge,service:a.service_name??"iMessage",displayName:a.display_name?.trim()||null,groupId:a.group_id||null,participants:s.get(a.rowid)??[],messageCount:a.message_count,recoverableCount:a.recoverable_count,unreadCount:a.unread_count,lastMessageAt:i?b(i.date):null,lastMessageRowId:a.last_rowid??0,lastMessageText:i?be(i.text,i.attributedBody):null,lastMessageFromMe:i?.is_from_me===1}}).sort((a,i)=>(i.lastMessageAt??0)-(a.lastMessageAt??0))}participantsByChat(){let e=this.db.query(`
12
+ `).all(),s=this.participantsByChat(),r=this.db.query("SELECT m.text, m.attributedBody, m.date, m.is_from_me FROM message m WHERE m.ROWID = ?");return t.map((i)=>{let a=i.last_rowid?r.get(i.last_rowid):null;return{rowid:i.rowid,guid:i.guid,identifier:i.chat_identifier?p(i.chat_identifier):i.guid,style:i.style??Qe,service:i.service_name??"iMessage",displayName:i.display_name?.trim()||null,groupId:i.group_id||null,participants:s.get(i.rowid)??[],messageCount:i.message_count,recoverableCount:i.recoverable_count,unreadCount:i.unread_count,lastMessageAt:a?y(a.date):null,lastMessageRowId:i.last_rowid??0,lastMessageText:a?Ce(a.text,a.attributedBody):null,lastMessageFromMe:a?.is_from_me===1}}).sort((i,a)=>(a.lastMessageAt??0)-(i.lastMessageAt??0))}participantsByChat(){let e=this.db.query(`
13
13
  SELECT chj.chat_id, h.id FROM chat_handle_join chj JOIN handle h ON h.ROWID = chj.handle_id
14
- `).all(),t=new Map;for(let s of e){let r=t.get(s.chat_id)??[],n=m(s.id);if(!r.includes(n))r.push(n);t.set(s.chat_id,r)}return t}messages(e,t={}){if(e.length===0)return[];let s=e.map(()=>"?").join(", "),r=t.newestFirst?"m.date DESC, m.ROWID DESC":t.byRowId?"m.ROWID ASC":"m.date ASC, m.ROWID ASC",n=t.beforeMs?`AND m.date < ${Je(t.beforeMs)}`:"",a=t.includeDeleted&&this.hasRecoverable?"(SELECT chat_id, message_id FROM chat_message_join UNION SELECT chat_id, message_id FROM chat_recoverable_message_join)":"chat_message_join",i=this.db.query(`
14
+ `).all(),t=new Map;for(let s of e){let r=t.get(s.chat_id)??[],n=p(s.id);if(!r.includes(n))r.push(n);t.set(s.chat_id,r)}return t}messages(e,t={}){if(e.length===0)return[];let s=e.map(()=>"?").join(", "),r=t.newestFirst?"m.date DESC, m.ROWID DESC":t.byRowId?"m.ROWID ASC":"m.date ASC, m.ROWID ASC",n=t.beforeMs?`AND m.date < ${et(t.beforeMs)}`:"",i=t.includeDeleted&&this.hasRecoverable?"(SELECT chat_id, message_id FROM chat_message_join UNION SELECT chat_id, message_id FROM chat_recoverable_message_join)":"chat_message_join",a=this.db.query(`
15
15
  SELECT
16
16
  m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.is_from_me, m.date, m.service,
17
17
  m.associated_message_guid, m.associated_message_type, m.item_type, m.group_title,
@@ -22,20 +22,20 @@ var Te=Object.defineProperty;var Re=(e)=>e;function Ie(e,t){this[e]=Re.bind(null
22
22
  h.id AS sender,
23
23
  c.guid AS chat_guid
24
24
  FROM message m
25
- JOIN ${a} cmj ON cmj.message_id = m.ROWID
25
+ JOIN ${i} cmj ON cmj.message_id = m.ROWID
26
26
  JOIN chat c ON c.ROWID = cmj.chat_id
27
27
  LEFT JOIN handle h ON h.ROWID = m.handle_id
28
28
  WHERE c.guid IN (${s}) AND m.ROWID > ? ${n}
29
29
  ORDER BY ${r}
30
30
  LIMIT ?
31
- `).all(...e,t.afterRowId??0,t.limit??1e6),o=this.attachmentsFor(i.filter((l)=>l.cache_has_attachments===1).map((l)=>l.rowid));return i.map((l)=>{let R=l.associated_message_type??0,u=l.item_type??0,p=ze(R)?"reaction":u!==0?"event":"message";return{rowid:l.rowid,guid:l.guid,chatGuid:l.chat_guid,kind:p,text:be(l.text,l.attributedBody),isFromMe:l.is_from_me===1,sentAt:b(l.date)??0,sender:l.sender?m(l.sender):null,service:l.service??"iMessage",targetGuid:p==="reaction"?Ke(l.associated_message_guid):null,reactionType:p==="reaction"?R:0,replyToGuid:l.reply_to_guid||null,editedAt:b(l.date_edited),unsent:!!l.date_retracted,eventType:u,groupTitle:l.group_title||null,attachments:o.get(l.rowid)??[]}})}attachmentPath(e){let t=this.db.query("SELECT filename, mime_type, transfer_name FROM attachment WHERE guid = ?").get(e);if(!t)return null;let s=ye(t.filename);return{path:s,mimeType:t.mime_type,name:t.transfer_name||(s?s.split("/").pop():e)}}attachmentsFor(e){let t=new Map;for(let s=0;s<e.length;s+=500){let r=e.slice(s,s+500),n=this.db.query(`
31
+ `).all(...e,t.afterRowId??0,t.limit??1e6),o=this.attachmentsFor(a.filter((l)=>l.cache_has_attachments===1).map((l)=>l.rowid));return a.map((l)=>{let g=l.associated_message_type??0,c=l.item_type??0,m=tt(g)?"reaction":c!==0?"event":"message";return{rowid:l.rowid,guid:l.guid,chatGuid:l.chat_guid,kind:m,text:Ce(l.text,l.attributedBody),isFromMe:l.is_from_me===1,sentAt:y(l.date)??0,sender:l.sender?p(l.sender):null,service:l.service??"iMessage",targetGuid:m==="reaction"?st(l.associated_message_guid):null,reactionType:m==="reaction"?g:0,replyToGuid:l.reply_to_guid||null,editedAt:y(l.date_edited),unsent:!!l.date_retracted,eventType:c,groupTitle:l.group_title||null,attachments:o.get(l.rowid)??[]}})}attachmentPath(e){let t=this.db.query("SELECT filename, mime_type, transfer_name FROM attachment WHERE guid = ?").get(e);if(!t)return null;let s=ke(t.filename);return{path:s,mimeType:t.mime_type,name:t.transfer_name||(s?s.split("/").pop():e)}}attachmentsFor(e){let t=new Map;for(let s=0;s<e.length;s+=500){let r=e.slice(s,s+500),n=this.db.query(`
32
32
  SELECT maj.message_id, a.guid, a.filename, a.transfer_name, a.mime_type, a.uti, a.total_bytes, a.is_sticker
33
33
  FROM message_attachment_join maj JOIN attachment a ON a.ROWID = maj.attachment_id
34
34
  WHERE maj.message_id IN (${r.map(()=>"?").join(", ")})
35
- `).all(...r);for(let a of n){let i=t.get(a.message_id)??[],o=ye(a.filename);i.push({guid:a.guid,path:o,name:a.transfer_name||(o?o.split("/").pop():a.guid),mimeType:a.mime_type,uti:a.uti,bytes:a.total_bytes??0,isSticker:a.is_sticker===1}),t.set(a.message_id,i)}}return t}}import{Database as Ve}from"bun:sqlite";import{existsSync as Me,readdirSync as Ye}from"fs";import{homedir as Xe}from"os";import{join as B}from"path";var te=B(Xe(),"Library","Application Support","AddressBook");function qe(e){let t=[],s=B(e,"AddressBook-v22.abcddb");if(Me(s))t.push(s);let r=B(e,"Sources");try{for(let n of Ye(r)){let a=B(r,n,"AddressBook-v22.abcddb");if(Me(a))t.push(a)}}catch{}return t}function Se(e){let t=e.replace(/[^\p{L}\p{N}\s]/gu," ").trim().split(/\s+/).filter(Boolean);if(t.length===0)return"?";let s=t[0][0],r=t.length>1?t[t.length-1][0]:"";return(s+r).toUpperCase()}class _{byHandle=new Map;static load(e=te){let t=new _;for(let s of qe(e))try{t.read(s)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new Ve(e,{readonly:!0});try{let s=t.query(`
36
- SELECT Z_PK AS id, ZFIRSTNAME AS first, ZLASTNAME AS last, ZNICKNAME AS nick, ZORGANIZATION AS org
35
+ `).all(...r);for(let i of n){let a=t.get(i.message_id)??[],o=ke(i.filename);a.push({guid:i.guid,path:o,name:i.transfer_name||(o?o.split("/").pop():i.guid),mimeType:i.mime_type,uti:i.uti,bytes:i.total_bytes??0,isSticker:i.is_sticker===1}),t.set(i.message_id,a)}}return t}}import{Database as Te}from"bun:sqlite";import{existsSync as Re,readdirSync as rt,readFileSync as nt}from"fs";import{homedir as it}from"os";import{dirname as at,join as E}from"path";var oe=E(it(),"Library","Application Support","AddressBook");function Ie(e,t){if(!t||t.length<2)return null;if(t[0]===1)return t.subarray(1);if(t[0]===2){let s=new TextDecoder().decode(t.subarray(1)).replace(/\0+$/,"").trim();if(!/^[\w-]+$/.test(s))return null;let r=E(at(e),".AddressBook-v22_SUPPORT","_EXTERNAL_DATA",s);try{return new Uint8Array(nt(r))}catch{return null}}return null}function ot(e){let t=[],s=E(e,"AddressBook-v22.abcddb");if(Re(s))t.push(s);let r=E(e,"Sources");try{for(let n of rt(r)){let i=E(r,n,"AddressBook-v22.abcddb");if(Re(i))t.push(i)}}catch{}return t}function De(e){let t=e.replace(/[^\p{L}\p{N}\s]/gu," ").trim().split(/\s+/).filter(Boolean);if(t.length===0)return"?";let s=t[0][0],r=t.length>1?t[t.length-1][0]:"";return(s+r).toUpperCase()}class T{byHandle=new Map;static load(e=oe){let t=new T;for(let s of ot(e))try{t.read(s)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new Te(e,{readonly:!0});try{let s=new Set(t.query("PRAGMA table_info(ZABCDRECORD)").all().map((c)=>c.name)),r=["ZTHUMBNAILIMAGEDATA","ZIMAGEDATA"].filter((c)=>s.has(c)),n=r.length>0?`(${r.map((c)=>`${c} IS NOT NULL`).join(" OR ")})`:"0",i=t.query(`
36
+ SELECT Z_PK AS id, ZFIRSTNAME AS first, ZLASTNAME AS last, ZNICKNAME AS nick, ZORGANIZATION AS org, ${n} AS hasPhoto
37
37
  FROM ZABCDRECORD
38
- `).all(),r=new Map;for(let i of s){let l=[i.first,i.last].filter(Boolean).join(" ").trim()||i.nick?.trim()||i.org?.trim();if(l)r.set(i.id,l)}let n=t.query("SELECT ZOWNER AS owner, ZFULLNUMBER AS value FROM ZABCDPHONENUMBER").all(),a=t.query("SELECT ZOWNER AS owner, ZADDRESS AS value FROM ZABCDEMAILADDRESS").all();for(let i of[...n,...a]){let o=r.get(i.owner);if(!o||!i.value)continue;let l=m(i.value);if(!this.byHandle.has(l))this.byHandle.set(l,{name:o,initials:Se(o)})}}finally{t.close()}}set(e,t){this.byHandle.set(m(e),{name:t,initials:Se(t)})}lookup(e){return this.byHandle.get(m(e))??null}nameFor(e){return this.lookup(e)?.name??null}}function Qe(e){if(e.style===W)return`group:${e.groupId||e.guid}`;return`direct:${e.identifier}`}function se(e){let t=new Map;for(let s of e){let r=Qe(s),n=t.get(r);if(!n){t.set(r,{key:r,kind:s.style===W?"group":"direct",chatGuids:[s.guid],displayName:s.displayName,groupId:s.groupId,participants:s.style===W?[...s.participants]:[s.identifier],messageCount:s.messageCount,recoverableCount:s.recoverableCount,unreadCount:s.unreadCount,lastMessageAt:s.lastMessageAt,lastMessageRowId:s.lastMessageRowId,lastMessageText:s.lastMessageText,lastMessageFromMe:s.lastMessageFromMe,services:[s.service]});continue}if(n.chatGuids.push(s.guid),n.messageCount+=s.messageCount,n.recoverableCount+=s.recoverableCount,n.unreadCount+=s.unreadCount,!n.services.includes(s.service))n.services.push(s.service);for(let a of s.participants)if(n.kind==="group"&&!n.participants.includes(a))n.participants.push(a);if((s.lastMessageAt??0)>(n.lastMessageAt??0))n.lastMessageAt=s.lastMessageAt,n.lastMessageRowId=s.lastMessageRowId,n.lastMessageText=s.lastMessageText,n.lastMessageFromMe=s.lastMessageFromMe,n.displayName=s.displayName??n.displayName}return[...t.values()].sort((s,r)=>(r.lastMessageAt??0)-(s.lastMessageAt??0))}function re(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!ge(t))return null;return`sms:${t}`}var st={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function rt(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}class w{provider="imessage";label="iMessage";databasePath;addressBookDir;openUrl;controller;confirmDeletes;platform;contacts=null;contactsLoadedAt=0;constructor(e={}){this.databasePath=e.databasePath??Q,this.addressBookDir=e.addressBookDir??te,this.openUrl=e.openUrl??rt,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??tt.platform}rowNames(e){let t=[];if(e.kind==="group")t.push(e.displayName);else if(e.participants[0])t.push(this.names().nameFor(e.participants[0]),F(e.participants[0]),e.participants[0]);return[...new Set(t.filter((s)=>!!s))]}open(e){let t=new ee(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new _;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=_.load(this.addressBookDir),this.contactsLoadedAt=Date.now();return this.contacts}person(e){return e?{id:e,name:this.names().nameFor(e)}:null}find(e,t){return se(e.chats()).find((s)=>s.key===t)}async status(){if(this.platform!=="darwin")return{connected:!1,detail:"iMessage is only available on a Mac.",needs:"unsupported-platform"};try{return this.open(()=>{return}),{connected:!0,detail:"Reading Messages on this Mac."}}catch(e){if(e instanceof U&&e.needsFullDiskAccess&&ne(et(this.databasePath)))return{connected:!1,detail:"Grant Full Disk Access in System Settings > Privacy & Security.",needs:"full-disk-access"};return{connected:!1,detail:"Sign in to Messages on this Mac.",needs:"messages-signed-out"}}}async conversations(){return this.open((e)=>se(e.chats()).map((t)=>this.toConversation(t)))}toConversation(e){let t=re(e);return{provider:"imessage",id:e.key,kind:e.kind,title:e.kind==="group"?e.displayName:null,participants:e.participants.map((s)=>this.person(s)),workspace:null,lastMessageAt:e.lastMessageAt,preview:e.messageCount>0?e.lastMessageText:null,previewFromMe:e.lastMessageFromMe,unread:e.unreadCount,cursor:String(e.lastMessageRowId),visible:e.messageCount>0,deleted:e.recoverableCount,archive:t?{mode:"confirm",detail:"Opens it in Messages at Delete Conversation for you to confirm. Messages keeps it in Recently Deleted for 30 days."}:{mode:"unsupported",detail:"Messages cannot open this conversation by address, so delete it there yourself."},url:t}}async messages(e,t={}){return this.open((s)=>{let r=this.find(s,e);if(!r)return[];let n=t.includeDeleted;return(t.after||!t.limit?s.messages(r.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:n}):s.messages(r.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:n}).reverse()).map((i)=>this.toMessage(e,i))})}toMessage(e,t){let s=t.reactionType>=3000;return{id:t.guid,conversationId:e,kind:t.kind,text:t.kind==="event"&&t.groupTitle?`Named the conversation \u201C${t.groupTitle}\u201D`:t.text,fromMe:t.isFromMe,sentAt:t.sentAt,sender:t.isFromMe?null:this.person(t.sender),targetId:t.targetGuid,reaction:t.kind==="reaction"?st[s?t.reactionType-1000:t.reactionType]??"\u2022":null,reactionRemoved:s,replyToId:t.replyToGuid,editedAt:t.editedAt,unsent:t.unsent,attachments:t.attachments.map((r)=>({id:r.guid,name:r.name,mimeType:r.mimeType,bytes:r.bytes,path:r.path&&ne(r.path)?r.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((n)=>this.find(n,e)),s=t?re(t):null;if(!t||!s)return{mode:"unsupported",removed:!1,detail:"Messages cannot open this conversation by address, so delete it there yourself."};if(t.messageCount===0)return{mode:"confirm",removed:!0,detail:"Already gone from Messages."};let r=this.rowNames(t);if(this.controller&&r.length>0){let n=await this.controller.remove(r,s,this.confirmDeletes);return{mode:"confirm",removed:!1,detail:n.ok?this.confirmDeletes?"Deleted in Messages.":"Messages is asking you to confirm the delete.":n.detail}}return this.openUrl(s),{mode:"confirm",removed:!1,detail:"Messages is open on the conversation: choose Conversation > Delete Conversation to finish."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>this.find(r,e));if(!t||t.messageCount>0)return;let s=this.rowNames(t);if(s.length>0)await this.controller.recover(s)}async attachment(e){if(!e.path||!ne(e.path))return new Response("Not found",{status:404});return new Response(Bun.file(e.path),{headers:{"content-type":e.mimeType??"application/octet-stream"}})}}var nt={"+1":"\uD83D\uDC4D",thumbsup:"\uD83D\uDC4D","-1":"\uD83D\uDC4E",heart:"\u2764\uFE0F",joy:"\uD83D\uDE02",laughing:"\uD83D\uDE06",tada:"\uD83C\uDF89",eyes:"\uD83D\uDC40",fire:"\uD83D\uDD25",white_check_mark:"\u2705",pray:"\uD83D\uDE4F",raised_hands:"\uD83D\uDE4C",rocket:"\uD83D\uDE80"},Z=(e)=>Math.round(Number(e)*1000);class H extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class E{provider="slack";label="Slack";token;fetch;apiBase;concurrency;self=null;users=new Map;channels=new Map;constructor(e){this.token=e.token,this.fetch=e.fetch??((t,s)=>fetch(t,s)),this.apiBase=(e.apiBase??"https://slack.com/api").replace(/\/$/,""),this.concurrency=e.concurrency??4}async api(e,t={},s=!1){for(let r=0;;r++){let n=new URLSearchParams(Object.entries(t).map(([o,l])=>[o,String(l)])),a=s?await this.fetch(`${this.apiBase}/${e}`,{method:"POST",headers:{authorization:`Bearer ${this.token}`,"content-type":"application/x-www-form-urlencoded"},body:n.toString()}):await this.fetch(`${this.apiBase}/${e}?${n}`,{headers:{authorization:`Bearer ${this.token}`}});if(a.status===429&&r<3){await Bun.sleep(Math.min(30,Number(a.headers.get("retry-after"))||1)*1000);continue}let i=await a.json();if(!i.ok)throw new H(e,i.error??`http_${a.status}`);return i}}async whoami(){if(!this.self){let e=await this.api("auth.test");this.self={userId:e.user_id,team:e.team,teamId:e.team_id}}return this.self}async userName(e){if(!e)return null;if(!this.users.has(e))try{let{user:t}=await this.api("users.info",{user:e});this.users.set(e,t.profile?.display_name||t.profile?.real_name||t.real_name||t.name||null)}catch{this.users.set(e,null)}return this.users.get(e)??null}async person(e){return e?{id:e,name:await this.userName(e)}:null}async status(){if(!this.token)return{connected:!1,detail:"Add a Slack user token.",needs:"token"};try{return{connected:!0,detail:`Signed in to ${(await this.whoami()).team}.`}}catch(e){return{connected:!1,detail:e instanceof Error?e.message:String(e),needs:"token"}}}archiveSupport(e){if(e.is_im||e.is_mpim)return{mode:"native",detail:"Closes the conversation in Slack. Slack reopens it when someone writes."};if(!e.is_private)return{mode:"native",detail:"Leaves the channel in Slack, and rejoins it if you unarchive."};return{mode:"unsupported",detail:"Leaving a private channel would need someone to invite you back, so it stays in Slack."}}async list(){let e=[],t="";do{let s=await this.api("users.conversations",{types:"im,mpim,public_channel,private_channel",exclude_archived:!0,limit:200,...t?{cursor:t}:{}});e.push(...s.channels),t=s.response_metadata?.next_cursor??""}while(t);return e}async conversations(){let e=await this.whoami(),t=await this.list(),s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((a)=>this.describe(a,e))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e,t){let[{channel:s},r]=await Promise.all([this.api("conversations.info",{channel:e.id}).catch(()=>({channel:e})),this.api("conversations.history",{channel:e.id,limit:1}).catch(()=>({messages:[]}))]),n={...e,...s};this.channels.set(n.id,n);let a=r.messages[0],i=[];if(n.is_im)i=[await this.person(n.user)];else if(n.is_mpim){let o=await this.api("conversations.members",{channel:n.id,limit:50}).catch(()=>({members:[]}));i=await Promise.all(o.members.filter((l)=>l!==t.userId).map(async(l)=>await this.person(l)))}return{provider:"slack",id:n.id,kind:n.is_im?"direct":n.is_mpim?"group":"channel",title:n.is_im||n.is_mpim?null:`#${n.name}`,participants:i,workspace:t.team,lastMessageAt:a?Z(a.ts):null,preview:a?.text||(a?.files?.length?"Attachment":null),previewFromMe:a?.user===t.userId,unread:n.unread_count_display??0,cursor:a?.ts??"0",visible:n.is_im||n.is_mpim?n.is_open!==!1:!0,archive:this.archiveSupport(n),url:`slack://channel?team=${t.teamId}&id=${n.id}`}}async messages(e,t={}){let s=await this.whoami(),r=[],n="",a=t.limit??1000;do{let o=await this.api("conversations.history",{channel:e,limit:Math.min(200,a),...t.after?{oldest:t.after,inclusive:!1}:{},...n?{cursor:n}:{}});r.push(...o.messages),n=o.has_more?o.response_metadata?.next_cursor??"":""}while(n&&r.length<a);r.sort((o,l)=>Number(o.ts)-Number(l.ts));let i=[];for(let o of r.slice(-a)){let l=await this.person(o.user),R=!!o.subtype&&!["file_share","thread_broadcast","me_message"].includes(o.subtype);i.push({id:o.ts,conversationId:e,kind:R?"event":"message",text:o.text??null,fromMe:o.user===s.userId,sentAt:Z(o.ts),sender:o.user===s.userId?null:l??(o.username?{id:o.bot_id??o.username,name:o.username}:null),targetId:null,reaction:null,reactionRemoved:!1,replyToId:o.thread_ts&&o.thread_ts!==o.ts?o.thread_ts:null,editedAt:o.edited?Z(o.edited.ts):null,unsent:!1,attachments:(o.files??[]).map((u)=>({id:u.id,name:u.name??u.title??u.id,mimeType:u.mimetype??null,bytes:u.size??0,path:null,url:u.url_private??null})),cursor:o.ts});for(let u of o.reactions??[])for(let p of u.users??[])i.push({id:`${o.ts}:${u.name}:${p}`,conversationId:e,kind:"reaction",text:null,fromMe:p===s.userId,sentAt:Z(o.ts),sender:p===s.userId?null:await this.person(p),targetId:o.ts,reaction:nt[u.name]??`:${u.name}:`,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:o.ts})}return i}async channel(e){let t=this.channels.get(e);if(t)return t;let{channel:s}=await this.api("conversations.info",{channel:e});return this.channels.set(e,s),s}async archive(e){let t=await this.channel(e),s=this.archiveSupport(t);if(s.mode!=="native")return{mode:s.mode,removed:!1,detail:s.detail};if(t.is_im||t.is_mpim)await this.api("conversations.close",{channel:e},!0);else await this.api("conversations.leave",{channel:e},!0);return this.channels.set(e,{...t,is_open:!1}),{mode:"native",removed:!0,detail:s.detail}}async unarchive(e){let t=await this.channel(e);if(t.is_im||t.is_mpim)await this.api("conversations.open",{channel:e},!0);else if(!t.is_private)await this.api("conversations.join",{channel:e},!0);this.channels.set(e,{...t,is_open:!0})}async attachment(e){if(!e.url)return new Response("Not found",{status:404});return this.fetch(e.url,{headers:{authorization:`Bearer ${this.token}`}})}}import{existsSync as le}from"fs";import{basename as ct,dirname as _e,extname as dt,join as we}from"path";import ut from"process";import{Database as at}from"bun:sqlite";import{homedir as it}from"os";import{join as ae}from"path";var Ce=ae(it(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),ie=ae(Ce,"ChatStorage.sqlite"),ke=ae(Ce,"Message"),G=0,ot=1,lt=4,c={Text:0,Image:1,Video:2,Audio:3,Contact:4,Location:5,GroupEvent:6,Link:7,Document:8,Call:10,Gif:11,Deleted:14,Sticker:15};class J extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="WhatsAppAccessError",this.needsFullDiskAccess=/authorization denied|not permitted|unable to open/i.test(s)}}function d(e){let t=e?.replace(/\uFFFC/g,"").trim();return t?t:null}class oe{path;db;constructor(e=ie){this.path=e;try{this.db=new at(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new J(e,t)}}close(){this.db.close()}sessions(){return this.db.query(`
38
+ `).all(),a=new Map,o=new Set;for(let c of i){let v=[c.first,c.last].filter(Boolean).join(" ").trim()||c.nick?.trim()||c.org?.trim();if(v)a.set(c.id,v);if(c.hasPhoto)o.add(c.id)}let l=t.query("SELECT ZOWNER AS owner, ZFULLNUMBER AS value FROM ZABCDPHONENUMBER").all(),g=t.query("SELECT ZOWNER AS owner, ZADDRESS AS value FROM ZABCDEMAILADDRESS").all();for(let c of[...l,...g]){let m=a.get(c.owner);if(!m||!c.value)continue;let v=p(c.value),X=o.has(c.owner)?{file:e,record:c.owner}:null,Y=this.byHandle.get(v);if(!Y)this.byHandle.set(v,{name:m,initials:De(m),photo:X});else if(!Y.photo&&X)Y.photo=X}}finally{t.close()}}set(e,t){this.byHandle.set(p(e),{name:t,initials:De(t)})}lookup(e){return this.byHandle.get(p(e))??null}nameFor(e){return this.lookup(e)?.name??null}hasPhoto(e){return!!this.lookup(e)?.photo}photo(e){let t=this.lookup(e)?.photo;if(!t)return null;try{let s=new Te(t.file,{readonly:!0});try{let r=s.query("SELECT ZTHUMBNAILIMAGEDATA AS thumb, ZIMAGEDATA AS full FROM ZABCDRECORD WHERE Z_PK = ?").get(t.record);return Ie(t.file,r?.thumb??null)??Ie(t.file,r?.full??null)}finally{s.close()}}catch{return null}}}function lt(e){if(e.style===H)return`group:${e.groupId||e.guid}`;return`direct:${e.identifier}`}function le(e){let t=new Map;for(let s of e){let r=lt(s),n=t.get(r);if(!n){t.set(r,{key:r,kind:s.style===H?"group":"direct",chatGuids:[s.guid],displayName:s.displayName,groupId:s.groupId,participants:s.style===H?[...s.participants]:[s.identifier],messageCount:s.messageCount,recoverableCount:s.recoverableCount,unreadCount:s.unreadCount,lastMessageAt:s.lastMessageAt,lastMessageRowId:s.lastMessageRowId,lastMessageText:s.lastMessageText,lastMessageFromMe:s.lastMessageFromMe,services:[s.service]});continue}if(n.chatGuids.push(s.guid),n.messageCount+=s.messageCount,n.recoverableCount+=s.recoverableCount,n.unreadCount+=s.unreadCount,!n.services.includes(s.service))n.services.push(s.service);for(let i of s.participants)if(n.kind==="group"&&!n.participants.includes(i))n.participants.push(i);if((s.lastMessageAt??0)>(n.lastMessageAt??0))n.lastMessageAt=s.lastMessageAt,n.lastMessageRowId=s.lastMessageRowId,n.lastMessageText=s.lastMessageText,n.lastMessageFromMe=s.lastMessageFromMe,n.displayName=s.displayName??n.displayName}return[...t.values()].sort((s,r)=>(r.lastMessageAt??0)-(s.lastMessageAt??0))}function ce(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!Me(t))return null;return`sms:${t}`}var dt={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function mt(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}class R{provider="imessage";label="iMessage";databasePath;addressBookDir;openUrl;controller;confirmDeletes;platform;contacts=null;contactsLoadedAt=0;constructor(e={}){this.databasePath=e.databasePath??ie,this.addressBookDir=e.addressBookDir??oe,this.openUrl=e.openUrl??mt,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??ut.platform}rowNames(e){let t=[];if(e.kind==="group")t.push(e.displayName);else if(e.participants[0])t.push(this.names().nameFor(e.participants[0]),B(e.participants[0]),e.participants[0]);return[...new Set(t.filter((s)=>!!s))]}open(e){let t=new ae(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new T;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=T.load(this.addressBookDir),this.contactsLoadedAt=Date.now();return this.contacts}person(e){if(!e)return null;let t=this.names();return{id:e,name:t.nameFor(e),avatar:t.hasPhoto(e)?e:null}}async avatar(e){return U(this.names().photo(e))}find(e,t){return le(e.chats()).find((s)=>s.key===t)}async status(){if(this.platform!=="darwin")return{connected:!1,detail:"iMessage is only available on a Mac.",needs:"unsupported-platform"};try{return this.open(()=>{return}),{connected:!0,detail:"Reading Messages on this Mac."}}catch(e){if(e instanceof G&&e.needsFullDiskAccess&&ue(ct(this.databasePath)))return{connected:!1,detail:"Grant Full Disk Access in System Settings > Privacy & Security.",needs:"full-disk-access"};return{connected:!1,detail:"Sign in to Messages on this Mac.",needs:"messages-signed-out"}}}async conversations(){return this.open((e)=>le(e.chats()).map((t)=>this.toConversation(t)))}toConversation(e){let t=ce(e);return{provider:"imessage",id:e.key,kind:e.kind,title:e.kind==="group"?e.displayName:null,participants:e.participants.map((s)=>this.person(s)),workspace:null,lastMessageAt:e.lastMessageAt,preview:e.messageCount>0?e.lastMessageText:null,previewFromMe:e.lastMessageFromMe,unread:e.unreadCount,cursor:String(e.lastMessageRowId),visible:e.messageCount>0,deleted:e.recoverableCount,archive:t?{mode:"confirm",detail:"Opens it in Messages at Delete Conversation for you to confirm. Messages keeps it in Recently Deleted for 30 days."}:{mode:"unsupported",detail:"Messages cannot open this conversation by address, so delete it there yourself."},url:t}}async messages(e,t={}){return this.open((s)=>{let r=this.find(s,e);if(!r)return[];let n=t.includeDeleted;return(t.after||!t.limit?s.messages(r.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:n}):s.messages(r.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:n}).reverse()).map((a)=>this.toMessage(e,a))})}toMessage(e,t){let s=t.reactionType>=3000;return{id:t.guid,conversationId:e,kind:t.kind,text:t.kind==="event"&&t.groupTitle?`Named the conversation \u201C${t.groupTitle}\u201D`:t.text,fromMe:t.isFromMe,sentAt:t.sentAt,sender:t.isFromMe?null:this.person(t.sender),targetId:t.targetGuid,reaction:t.kind==="reaction"?dt[s?t.reactionType-1000:t.reactionType]??"\u2022":null,reactionRemoved:s,replyToId:t.replyToGuid,editedAt:t.editedAt,unsent:t.unsent,attachments:t.attachments.map((r)=>({id:r.guid,name:r.name,mimeType:r.mimeType,bytes:r.bytes,path:r.path&&ue(r.path)?r.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((n)=>this.find(n,e)),s=t?ce(t):null;if(!t||!s)return{mode:"unsupported",removed:!1,detail:"Messages cannot open this conversation by address, so delete it there yourself."};if(t.messageCount===0)return{mode:"confirm",removed:!0,detail:"Already gone from Messages."};let r=this.rowNames(t);if(this.controller&&r.length>0){let n=await this.controller.remove(r,s,this.confirmDeletes);return{mode:"confirm",removed:!1,detail:n.ok?this.confirmDeletes?"Deleted in Messages.":"Messages is asking you to confirm the delete.":n.detail}}return this.openUrl(s),{mode:"confirm",removed:!1,detail:"Messages is open on the conversation: choose Conversation > Delete Conversation to finish."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>this.find(r,e));if(!t||t.messageCount>0)return;let s=this.rowNames(t);if(s.length>0)await this.controller.recover(s)}async attachment(e){if(!e.path||!ue(e.path))return new Response("Not found",{status:404});return new Response(Bun.file(e.path),{headers:{"content-type":e.mimeType??"application/octet-stream"}})}}var pt="im,mpim,public_channel,private_channel",ht={"+1":"\uD83D\uDC4D",thumbsup:"\uD83D\uDC4D","-1":"\uD83D\uDC4E",heart:"\u2764\uFE0F",joy:"\uD83D\uDE02",laughing:"\uD83D\uDE06",tada:"\uD83C\uDF89",eyes:"\uD83D\uDC40",fire:"\uD83D\uDD25",white_check_mark:"\u2705",pray:"\uD83D\uDE4F",raised_hands:"\uD83D\uDE4C",rocket:"\uD83D\uDE80"},J=(e)=>Math.round(Number(e)*1000);class z extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class I{provider="slack";label="Slack";token;fetch;apiBase;concurrency;self=null;users=new Map;avatars=new Map;channels=new Map;constructor(e){this.token=e.token,this.fetch=e.fetch??((t,s)=>fetch(t,s)),this.apiBase=(e.apiBase??"https://slack.com/api").replace(/\/$/,""),this.concurrency=e.concurrency??4}async api(e,t={},s=!1){for(let r=0;;r++){let n=new URLSearchParams(Object.entries(t).map(([o,l])=>[o,String(l)])),i=s?await this.fetch(`${this.apiBase}/${e}`,{method:"POST",headers:{authorization:`Bearer ${this.token}`,"content-type":"application/x-www-form-urlencoded"},body:n.toString()}):await this.fetch(`${this.apiBase}/${e}?${n}`,{headers:{authorization:`Bearer ${this.token}`}});if(i.status===429&&r<3){await Bun.sleep(Math.min(30,Number(i.headers.get("retry-after"))||1)*1000);continue}let a=await i.json();if(!a.ok)throw new z(e,a.error??`http_${i.status}`);return a}}async whoami(){if(!this.self){let e=await this.api("auth.test");this.self={userId:e.user_id,team:e.team,teamId:e.team_id}}return this.self}async userName(e){if(!e)return null;if(!this.users.has(e))try{let{user:t}=await this.api("users.info",{user:e});this.users.set(e,t.profile?.display_name||t.profile?.real_name||t.real_name||t.name||null),this.avatars.set(e,t.profile?.image_72??null)}catch{this.users.set(e,null)}return this.users.get(e)??null}async person(e){if(!e)return null;let t=await this.userName(e);return{id:e,name:t,avatar:this.avatars.get(e)??null}}async avatar(e){if(!W(e,["slack-edge.com","slack.com","gravatar.com"]))return new Response("Not found",{status:404});return this.fetch(e)}async status(){if(!this.token)return{connected:!1,detail:"Add a Slack user token.",needs:"token"};try{return{connected:!0,detail:`Signed in to ${(await this.whoami()).team}.`}}catch(e){return{connected:!1,detail:e instanceof Error?e.message:String(e),needs:"token"}}}archiveSupport(e){if(e.is_im||e.is_mpim)return{mode:"native",detail:"Closes the conversation in Slack. Slack reopens it when someone writes."};if(!e.is_private)return{mode:"native",detail:"Leaves the channel in Slack, and rejoins it if you unarchive."};return{mode:"unsupported",detail:"Leaving a private channel would need someone to invite you back, so it stays in Slack."}}async list(){let e=[],t="";do{let s=await this.api("users.conversations",{types:pt,exclude_archived:!0,limit:200,...t?{cursor:t}:{}});e.push(...s.channels),t=s.response_metadata?.next_cursor??""}while(t);return e}async conversations(){let e=await this.whoami(),t=await this.list(),s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,e))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e,t){let[{channel:s},r]=await Promise.all([this.api("conversations.info",{channel:e.id}).catch(()=>({channel:e})),this.api("conversations.history",{channel:e.id,limit:1}).catch(()=>({messages:[]}))]),n={...e,...s};this.channels.set(n.id,n);let i=r.messages[0],a=[];if(n.is_im)a=[await this.person(n.user)];else if(n.is_mpim){let o=await this.api("conversations.members",{channel:n.id,limit:50}).catch(()=>({members:[]}));a=await Promise.all(o.members.filter((l)=>l!==t.userId).map(async(l)=>await this.person(l)))}return{provider:"slack",id:n.id,kind:n.is_im?"direct":n.is_mpim?"group":"channel",title:n.is_im||n.is_mpim?null:`#${n.name}`,participants:a,workspace:t.team,lastMessageAt:i?J(i.ts):null,preview:i?.text||(i?.files?.length?"Attachment":null),previewFromMe:i?.user===t.userId,unread:n.unread_count_display??0,cursor:i?.ts??"0",visible:n.is_im||n.is_mpim?n.is_open!==!1:!0,archive:this.archiveSupport(n),url:`slack://channel?team=${t.teamId}&id=${n.id}`}}async messages(e,t={}){let s=await this.whoami(),r=[],n="",i=t.limit??1000;do{let o=await this.api("conversations.history",{channel:e,limit:Math.min(200,i),...t.after?{oldest:t.after,inclusive:!1}:{},...n?{cursor:n}:{}});r.push(...o.messages),n=o.has_more?o.response_metadata?.next_cursor??"":""}while(n&&r.length<i);r.sort((o,l)=>Number(o.ts)-Number(l.ts));let a=[];for(let o of r.slice(-i)){let l=await this.person(o.user),g=!!o.subtype&&!["file_share","thread_broadcast","me_message"].includes(o.subtype);a.push({id:o.ts,conversationId:e,kind:g?"event":"message",text:o.text??null,fromMe:o.user===s.userId,sentAt:J(o.ts),sender:o.user===s.userId?null:l??(o.username?{id:o.bot_id??o.username,name:o.username}:null),targetId:null,reaction:null,reactionRemoved:!1,replyToId:o.thread_ts&&o.thread_ts!==o.ts?o.thread_ts:null,editedAt:o.edited?J(o.edited.ts):null,unsent:!1,attachments:(o.files??[]).map((c)=>({id:c.id,name:c.name??c.title??c.id,mimeType:c.mimetype??null,bytes:c.size??0,path:null,url:c.url_private??null})),cursor:o.ts});for(let c of o.reactions??[])for(let m of c.users??[])a.push({id:`${o.ts}:${c.name}:${m}`,conversationId:e,kind:"reaction",text:null,fromMe:m===s.userId,sentAt:J(o.ts),sender:m===s.userId?null:await this.person(m),targetId:o.ts,reaction:ht[c.name]??`:${c.name}:`,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:o.ts})}return a}async channel(e){let t=this.channels.get(e);if(t)return t;let{channel:s}=await this.api("conversations.info",{channel:e});return this.channels.set(e,s),s}async archive(e){let t=await this.channel(e),s=this.archiveSupport(t);if(s.mode!=="native")return{mode:s.mode,removed:!1,detail:s.detail};if(t.is_im||t.is_mpim)await this.api("conversations.close",{channel:e},!0);else await this.api("conversations.leave",{channel:e},!0);return this.channels.set(e,{...t,is_open:!1}),{mode:"native",removed:!0,detail:s.detail}}async unarchive(e){let t=await this.channel(e);if(t.is_im||t.is_mpim)await this.api("conversations.open",{channel:e},!0);else if(!t.is_private)await this.api("conversations.join",{channel:e},!0);this.channels.set(e,{...t,is_open:!0})}async attachment(e){if(!e.url)return new Response("Not found",{status:404});return this.fetch(e.url,{headers:{authorization:`Bearer ${this.token}`}})}}import{existsSync as q,readFileSync as vt}from"fs";import{basename as At,dirname as he,extname as xt,join as ge}from"path";import Mt from"process";import{Database as gt}from"bun:sqlite";import{homedir as ft}from"os";import{join as de}from"path";var Pe=de(ft(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),me=de(Pe,"ChatStorage.sqlite"),Oe=de(Pe,"Message"),K=0,bt=1,yt=4,u={Text:0,Image:1,Video:2,Audio:3,Contact:4,Location:5,GroupEvent:6,Link:7,Document:8,Call:10,Gif:11,Deleted:14,Sticker:15};class V extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="WhatsAppAccessError",this.needsFullDiskAccess=/authorization denied|not permitted|unable to open/i.test(s)}}function d(e){let t=e?.replace(/\uFFFC/g,"").trim();return t?t:null}class pe{path;db;constructor(e=me){this.path=e;try{this.db=new gt(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new V(e,t)}}close(){this.db.close()}sessions(){return this.db.query(`
39
39
  SELECT s.Z_PK AS pk, s.ZCONTACTJID AS jid, s.ZSESSIONTYPE AS type, s.ZPARTNERNAME AS name,
40
40
  s.ZARCHIVED AS archived, s.ZUNREADCOUNT AS unread, s.ZLASTMESSAGEDATE AS lastDate,
41
41
  s.ZLASTMESSAGETEXT AS lastText, COALESCE(last.ZISFROMME, 0) AS lastFromMe,
@@ -43,11 +43,11 @@ var Te=Object.defineProperty;var Re=(e)=>e;function Ie(e,t){this[e]=Re.bind(null
43
43
  (SELECT COUNT(*) FROM ZWAMESSAGE m WHERE m.ZCHATSESSION = s.Z_PK) AS messageCount
44
44
  FROM ZWACHATSESSION s
45
45
  LEFT JOIN ZWAMESSAGE last ON last.Z_PK = s.ZLASTMESSAGE
46
- WHERE s.ZSESSIONTYPE IN (${G}, ${ot}, ${lt})
46
+ WHERE s.ZSESSIONTYPE IN (${K}, ${bt}, ${yt})
47
47
  AND COALESCE(s.ZHIDDEN, 0) = 0 AND COALESCE(s.ZREMOVED, 0) = 0
48
48
  AND s.ZCONTACTJID IS NOT NULL
49
49
  ORDER BY s.ZLASTMESSAGEDATE DESC
50
- `).all().map((t)=>({pk:t.pk,jid:t.jid,type:t.type,name:d(t.name),archived:!!t.archived,unread:t.unread==null?0:t.unread<0?1:t.unread,lastMessageAt:b(t.lastDate),lastMessageText:d(t.lastText),lastMessageFromMe:!!t.lastFromMe,lastMessagePk:t.lastPk,messageCount:t.messageCount}))}session(e){return this.sessions().find((t)=>t.jid===e)}members(e){return this.db.query(`
50
+ `).all().map((t)=>({pk:t.pk,jid:t.jid,type:t.type,name:d(t.name),archived:!!t.archived,unread:t.unread==null?0:t.unread<0?1:t.unread,lastMessageAt:y(t.lastDate),lastMessageText:d(t.lastText),lastMessageFromMe:!!t.lastFromMe,lastMessagePk:t.lastPk,messageCount:t.messageCount}))}session(e){return this.sessions().find((t)=>t.jid===e)}profilePicturePaths(){try{let e=this.db.query("SELECT ZJID AS jid, ZPATH AS path FROM ZWAPROFILEPICTUREITEM WHERE ZJID IS NOT NULL AND ZPATH IS NOT NULL").all();return new Map(e.map((t)=>[t.jid,t.path]))}catch{return new Map}}members(e){return this.db.query(`
51
51
  SELECT g.ZMEMBERJID AS jid, COALESCE(g.ZCONTACTNAME, g.ZFIRSTNAME, p.ZPUSHNAME) AS name
52
52
  FROM ZWAGROUPMEMBER g
53
53
  LEFT JOIN ZWAPROFILEPUSHNAME p ON p.ZJID = g.ZMEMBERJID
@@ -66,4 +66,4 @@ var Te=Object.defineProperty;var Re=(e)=>e;function Ie(e,t){this[e]=Re.bind(null
66
66
  WHERE m.ZCHATSESSION = ? AND m.Z_PK > ?
67
67
  ORDER BY m.Z_PK ${s}
68
68
  ${r}
69
- `).all(e,t.afterPk??0).map((a)=>({pk:a.pk,stanzaId:a.stanzaId,type:a.type,text:a.text,isFromMe:!!a.isFromMe,sentAt:b(a.date)??0,senderJid:a.senderJid,senderName:d(a.senderName),media:a.mediaPk==null?null:{path:a.mediaPath,vcard:a.vcard,title:a.title,bytes:a.bytes??0,latitude:a.latitude,longitude:a.longitude}}))}}var mt="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",pt={[c.Image]:"image/jpeg",[c.Video]:"video/mp4",[c.Audio]:"audio/ogg",[c.Gif]:"video/mp4",[c.Sticker]:"image/webp"},ht={[c.Image]:"Photo",[c.Video]:"Video",[c.Audio]:"Voice message",[c.Document]:"Document",[c.Gif]:"GIF",[c.Sticker]:"Sticker"},gt={".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 ft(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function ce(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function bt(e){let t=e?.match(/^FN:(.*)$/m);return d(t?.[1])}class T{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??ie,this.mediaRoot=e.mediaRoot??(e.databasePath?we(_e(e.databasePath),"Message"):ke),this.controller=e.controller,this.openUrl=e.openUrl??ft,this.platform=e.platform??ut.platform,this.settleMs=e.settleMs??6000}open(e){let t=new oe(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 J&&e.needsFullDiskAccess&&le(_e(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:mt};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)=>e.sessions().map((t)=>{let s=t.type===G,r=s?[{id:t.jid,name:t.name}]:e.members(t.pk).map((n)=>({id:n.jid,name:n.name}));return{provider:"whatsapp",id:t.jid,kind:s?"direct":"group",title:s?null:t.name,participants:r,workspace:null,lastMessageAt:t.lastMessageAt,preview:t.lastMessageText,previewFromMe:t.lastMessageFromMe,unread:t.unread,cursor:String(t.lastMessagePk),visible:!t.archived,archive:this.archiveSupport(t),url:this.url(t)}}))}async messages(e,t={}){return this.open((s)=>{let r=s.session(e);if(!r)return[];return(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()).map((a)=>this.toMessage(r,a))})}toMessage(e,t){let s=t.isFromMe?null:e.type===G?{id:e.jid,name:e.name}:t.senderJid?{id:t.senderJid,name:t.senderName}:null,{kind:r,text:n,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:r,text:n,fromMe:t.isFromMe,sentAt:t.sentAt,sender:s,targetId:null,reaction:null,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}describe(e){let t=e.media;switch(e.type){case c.Text:case c.Link:return{kind:"message",text:d(e.text),unsent:!1};case c.Image:case c.Video:case c.Gif:return{kind:"message",text:d(e.text)??d(t?.title),unsent:!1};case c.Contact:{let s=bt(t?.vcard??null)??d(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case c.Location:{let s=d(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 c.GroupEvent:case c.Call:return{kind:"event",text:d(e.text),unsent:!1};case c.Deleted:return{kind:"message",text:null,unsent:!0};case c.Audio:case c.Document:case c.Sticker:return{kind:"message",text:null,unsent:!1};default:return d(e.text)||t?.path?{kind:"message",text:d(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=ht[e.type];if(!t||!t.path&&!s||e.type===c.Text||e.type===c.Link)return[];let r=t.path?we(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,a=t.path?ct(t.path):null,i=e.type===c.Document?d(t.title)??d(e.text)??a??s:a??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:i,mimeType:n??(t.path?gt[dt(t.path).toLowerCase()]:void 0)??pt[e.type]??null,bytes:t.bytes,path:r&&le(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,ce(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||!le(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 yt(e,t){switch(e){case"imessage":return new w(t);case"slack":return new E(t);case"discord":return new k(t);case"whatsapp":return new T(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}async function Ms(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return P(e);case"discord":return D(e);case"teams":return N(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Ss(e,t,s){return O(e,t,s)}async function Cs(e,t,s){return A(e,t,s)}async function ks(e,t){return L(e,t)}function _s(e){x(e)}function ws(e){y(e)}function Es(e){S(e)}export{ws as configureDiscord,_s as configureSlack,Es as configureTeams,z as discord,Ee as inbox,Ms as send,Cs as sendToDiscord,Ss as sendToSlack,ks as sendToTeams,K as slack,Y as teams};
69
+ `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:y(i.date)??0,senderJid:i.senderJid,senderName:d(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}}))}}var St="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",Ct={[u.Image]:"image/jpeg",[u.Video]:"video/mp4",[u.Audio]:"audio/ogg",[u.Gif]:"video/mp4",[u.Sticker]:"image/webp"},kt={[u.Image]:"Photo",[u.Video]:"Video",[u.Audio]:"Voice message",[u.Document]:"Document",[u.Gif]:"GIF",[u.Sticker]:"Sticker"},_t={".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 wt(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function fe(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function Et(e){let t=e?.match(/^FN:(.*)$/m);return d(t?.[1])}class D{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??me,this.mediaRoot=e.mediaRoot??(e.databasePath?ge(he(e.databasePath),"Message"):Oe),this.controller=e.controller,this.openUrl=e.openUrl??wt,this.platform=e.platform??Mt.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=ge(he(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>q(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 U(t?new Uint8Array(vt(t)):null)}open(e){let t=new pe(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 V&&e.needsFullDiskAccess&&q(he(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:St};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===K,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);return n.map((a)=>this.toMessage(r,a,i))})}toMessage(e,t,s){let r=t.isFromMe?null:e.type===K?{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:null,editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}describe(e){let t=e.media;switch(e.type){case u.Text:case u.Link:return{kind:"message",text:d(e.text),unsent:!1};case u.Image:case u.Video:case u.Gif:return{kind:"message",text:d(e.text)??d(t?.title),unsent:!1};case u.Contact:{let s=Et(t?.vcard??null)??d(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case u.Location:{let s=d(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 u.GroupEvent:case u.Call:return{kind:"event",text:d(e.text),unsent:!1};case u.Deleted:return{kind:"message",text:null,unsent:!0};case u.Audio:case u.Document:case u.Sticker:return{kind:"message",text:null,unsent:!1};default:return d(e.text)||t?.path?{kind:"message",text:d(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=kt[e.type];if(!t||!t.path&&!s||e.type===u.Text||e.type===u.Link)return[];let r=t.path?ge(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?At(t.path):null,a=e.type===u.Document?d(t.title)??d(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?_t[xt(t.path).toLowerCase()]:void 0)??Ct[e.type]??null,bytes:t.bytes,path:r&&q(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,fe(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||!q(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 Tt(e,t){switch(e){case"imessage":return new R(t);case"slack":return new I(t);case"discord":return new w(t);case"whatsapp":return new D(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}async function $s(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return N(e);case"discord":return O(e);case"teams":return F(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function js(e,t,s){return L(e,t,s)}async function Us(e,t,s){return M(e,t,s)}async function Ws(e,t){return j(e,t)}function Bs(e){S(e)}function Zs(e){A(e)}function Hs(e){k(e)}export{Zs as configureDiscord,Bs as configureSlack,Hs as configureTeams,Q as discord,Ne as inbox,$s as send,Us as sendToDiscord,js as sendToSlack,Ws as sendToTeams,ee as slack,se 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.52",
5
+ "version": "0.75.53",
6
6
  "description": "Easily interact with chat APIs.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,13 +58,13 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "dependencies": {
61
- "@stacksjs/logging": "0.75.52"
61
+ "@stacksjs/logging": "0.75.53"
62
62
  },
63
63
  "devDependencies": {
64
- "@stacksjs/cli": "0.75.52",
65
- "@stacksjs/config": "0.75.52",
66
- "@stacksjs/error-handling": "0.75.52",
67
- "@stacksjs/types": "0.75.52",
64
+ "@stacksjs/cli": "0.75.53",
65
+ "@stacksjs/config": "0.75.53",
66
+ "@stacksjs/error-handling": "0.75.53",
67
+ "@stacksjs/types": "0.75.53",
68
68
  "better-dx": "^0.2.24"
69
69
  }
70
70
  }