@stacksjs/chat 0.75.63 → 0.75.65

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.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Discord's gateway, as a user account's client holds it: one WebSocket that
3
+ * delivers the account's state on connecting (READY: DMs, servers, read
4
+ * states) and every event after it.
5
+ *
6
+ * The protocol, in brief: the server says HELLO with a heartbeat interval;
7
+ * the client heartbeats with the last sequence number it saw and IDENTIFYs
8
+ * (or RESUMEs a dropped session). Events arrive as op 0 with a type and a
9
+ * sequence number. Op 7 asks the client to reconnect; op 9 says the session
10
+ * is gone and a fresh IDENTIFY is needed.
11
+ */
12
+ export declare interface GatewayIdentity {
13
+ token: string
14
+ properties: Record<string, unknown>
15
+ capabilities?: number
16
+ }
17
+ export declare interface GatewayOptions {
18
+ url?: string
19
+ WebSocket?: new (url: string) => WebSocket
20
+ onDispatch: (type: string, data: any) => void
21
+ onAuthFailed?: (reason: string) => void
22
+ }
23
+ export declare class DiscordGateway {
24
+ ready: any;
25
+ constructor(identity: GatewayIdentity, options: GatewayOptions);
26
+ whenReady(timeoutMs?: unknown): Promise<any>;
27
+ connect(): void;
28
+ stop(): void;
29
+ }
@@ -0,0 +1,19 @@
1
+ export declare function channelPermissions(input: {
2
+ guildId: string
3
+ ownerId?: string | null
4
+ userId: string
5
+ memberRoles: string[]
6
+ roles: RoleLike[]
7
+ overwrites: OverwriteLike[]
8
+ }): bigint;
9
+ export declare function canRead(permissions: bigint): boolean;
10
+ /**
11
+ * Whether a member can see a channel, by Discord's own rules: the server's
12
+ * @everyone role and the member's roles set the base, Administrator sees
13
+ * everything, and the channel's overwrites then apply in order - @everyone,
14
+ * then the member's roles together, then the member themselves.
15
+ */
16
+ export declare const VIEW_CHANNEL: boolean;
17
+ export declare const READ_MESSAGE_HISTORY: boolean;
18
+ export declare interface RoleLike { id: string, permissions: string | number }
19
+ export declare interface OverwriteLike { id: string, type: number | string, allow: string | number, deny: string | number }
@@ -1,32 +1,43 @@
1
- import type { ArchiveOutcome, Fetch, InboxAttachment, InboxConversation, InboxDriver, InboxMessage, InboxStatus, MessageQuery } from './types';
1
+ import type { ArchiveOutcome, Fetch, InboxAttachment, InboxCapabilities, InboxConversation, InboxDriver, InboxEvent, InboxMessage, InboxStatus, MessageQuery, OutgoingMessage, SendOutcome } from './types';
2
2
  /** Discord ids are snowflakes: milliseconds since 2015 in the high bits. */
3
3
  export declare function snowflakeTime(id: string | null | undefined): number | null;
4
4
  export declare interface DiscordInboxConfig {
5
5
  token: string
6
+ account?: 'bot' | 'user'
6
7
  userId?: string
7
8
  fetch?: Fetch
8
9
  apiBase?: string
9
10
  concurrency?: number
11
+ WebSocket?: new (url: string) => WebSocket
12
+ gatewayUrl?: string
10
13
  }
11
14
  /**
12
- * Discord, through the official API with a bot token.
15
+ * Discord, through the official API.
13
16
  *
14
- * What a bot can see is what Discord allows: the channels and threads of the
15
- * servers it has been added to. Personal DMs are out of reach - only a user
16
- * token reads them, and automating a user account breaks Discord's terms and
17
- * gets accounts banned - so this driver does not offer them.
17
+ * With a bot token it reads the channels and threads of the servers the bot
18
+ * is in. With the person's own token (`account: 'user'`) it is their client:
19
+ * DMs, group DMs, and the server channels they can read, listed from the
20
+ * gateway's READY and kept current by its events, with sending, reactions and
21
+ * read state through the API.
18
22
  *
19
- * Archiving a thread archives it in Discord, which is Discord's own way of
20
- * putting one away (it reopens when someone posts). A channel cannot be hidden
21
- * for one person through the API, so it is archived in the inbox only.
23
+ * Archiving a DM closes it, as the client's own Close DM does, and it comes
24
+ * back when someone writes. A group DM can only be left, which loses it, so
25
+ * like a server channel it is archived in the inbox alone. A bot archives a
26
+ * thread in Discord.
22
27
  */
23
28
  export declare class DiscordInboxDriver implements InboxDriver {
24
29
  readonly provider: 'discord';
25
30
  readonly label: string;
26
31
  constructor(config: DiscordInboxConfig);
32
+ capabilities(): InboxCapabilities;
27
33
  status(): Promise<InboxStatus>;
34
+ watch(onEvent: (event: InboxEvent) => void): () => void;
35
+ close(): void;
28
36
  conversations(): Promise<InboxConversation[]>;
29
37
  messages(conversationId: string, query?: MessageQuery): Promise<InboxMessage[]>;
38
+ send(conversationId: string, message: OutgoingMessage): Promise<SendOutcome>;
39
+ react(conversationId: string, messageId: string, reaction: string, remove?: boolean): Promise<void>;
40
+ markRead(conversationId: string): Promise<void>;
30
41
  archive(conversationId: string): Promise<ArchiveOutcome>;
31
42
  unarchive(conversationId: string): Promise<void>;
32
43
  avatar(ref: string): Promise<Response>;
@@ -1,4 +1,4 @@
1
- import type { ArchiveOutcome, InboxAttachment, InboxConversation, InboxDriver, InboxMessage, InboxStatus, MessageQuery } from '../types';
1
+ import type { ArchiveOutcome, InboxAttachment, InboxCapabilities, InboxConversation, InboxDriver, InboxMessage, InboxStatus, MessageQuery, OutgoingMessage, SendOutcome } from '../types';
2
2
  /**
3
3
  * Something that can drive Messages' own Delete and Recover for one named
4
4
  * conversation - in Attic, a small Swift helper bundled with the app that
@@ -17,6 +17,7 @@ export declare interface IMessageConfig {
17
17
  addressBookDir?: string | false
18
18
  openUrl?: (url: string) => void
19
19
  platform?: NodeJS.Platform
20
+ runScript?: (script: string, args: string[]) => Promise<{ ok: boolean, output: string }>
20
21
  }
21
22
  /**
22
23
  * iMessage and SMS, read from the Messages database on this Mac.
@@ -35,6 +36,8 @@ export declare class IMessageDriver implements InboxDriver {
35
36
  readonly provider: 'imessage';
36
37
  readonly label: string;
37
38
  constructor(config?: IMessageConfig);
39
+ capabilities(): InboxCapabilities;
40
+ send(conversationId: string, message: OutgoingMessage): Promise<SendOutcome>;
38
41
  avatar(ref: string): Promise<Response>;
39
42
  status(): Promise<InboxStatus>;
40
43
  conversations(): Promise<InboxConversation[]>;
@@ -1,6 +1,8 @@
1
- import type { ArchiveOutcome, Fetch, InboxAttachment, InboxConversation, InboxDriver, InboxMessage, InboxStatus, MessageQuery } from './types';
1
+ import type { ArchiveOutcome, Fetch, InboxAttachment, InboxCapabilities, InboxConversation, InboxDriver, InboxEvent, InboxMessage, InboxStatus, MessageQuery, OutgoingMessage, SendOutcome } from './types';
2
2
  export declare interface SlackInboxConfig {
3
3
  token: string
4
+ appToken?: string
5
+ WebSocket?: new (url: string) => WebSocket
4
6
  fetch?: Fetch
5
7
  apiBase?: string
6
8
  concurrency?: number
@@ -23,6 +25,11 @@ export declare class SlackInboxDriver implements InboxDriver {
23
25
  readonly provider: 'slack';
24
26
  readonly label: string;
25
27
  constructor(config: SlackInboxConfig);
28
+ capabilities(): InboxCapabilities;
29
+ send(conversationId: string, message: OutgoingMessage): Promise<SendOutcome>;
30
+ react(conversationId: string, messageId: string, reaction: string, remove?: boolean): Promise<void>;
31
+ markRead(conversationId: string): Promise<void>;
32
+ watch(onEvent: (event: InboxEvent) => void): () => void;
26
33
  avatar(ref: string): Promise<Response>;
27
34
  status(): Promise<InboxStatus>;
28
35
  conversations(): Promise<InboxConversation[]>;
@@ -76,6 +76,31 @@ export declare interface InboxDriver {
76
76
  unarchive: (conversationId: string) => Promise<void>
77
77
  attachment: (attachment: InboxAttachment) => Promise<Response>
78
78
  avatar?: (ref: string) => Promise<Response>
79
+ capabilities?: () => InboxCapabilities
80
+ send?: (conversationId: string, message: OutgoingMessage) => Promise<SendOutcome>
81
+ react?: (conversationId: string, messageId: string, reaction: string, remove?: boolean) => Promise<void>
82
+ markRead?: (conversationId: string) => Promise<void>
83
+ watch?: (onEvent: (event: InboxEvent) => void) => () => void
84
+ }
85
+ /** A message to send into a conversation. */
86
+ export declare interface OutgoingMessage {
87
+ text?: string
88
+ replyToId?: string | null
89
+ files?: Array<{ path: string, name?: string, mimeType?: string | null }>
90
+ }
91
+ /** What became of a sent message. */
92
+ export declare interface SendOutcome {
93
+ id: string | null
94
+ sentAt: number
95
+ }
96
+ /** What a driver can do beyond reading, so a client offers only what works. */
97
+ export declare interface InboxCapabilities {
98
+ send: boolean
99
+ attachments: boolean
100
+ replies: boolean
101
+ reactions: boolean
102
+ markRead: boolean
103
+ live: boolean
79
104
  }
80
105
  /**
81
106
  * The inbox side of `@stacksjs/chat`: reading conversations out of the apps
@@ -101,5 +126,12 @@ export type InboxProvider = 'imessage' | 'slack' | 'discord' | 'whatsapp';
101
126
  * conversation can still be archived in the inbox itself.
102
127
  */
103
128
  export type ArchiveMode = 'native' | 'confirm' | 'unsupported';
129
+ /**
130
+ * Something that happened in the real app, as it happens: a new message, or a
131
+ * change the inbox should read again (an edit, a reaction, a conversation
132
+ * read elsewhere).
133
+ */
134
+ export type InboxEvent = | { type: 'message', conversationId: string, message: InboxMessage }
135
+ | { type: 'changed', conversationId: string | null }
104
136
  /** A `fetch` the HTTP drivers call, so tests and proxies can stand in. */
105
137
  export type Fetch = (input: string, init?: RequestInit) => Promise<Response>;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var Ke=Object.defineProperty;var qe=(e)=>e;function Xe(e,t){this[e]=qe.bind(null,t)}var z=(e,t)=>{for(var r in t)Ke(e,r,{get:t[r],enumerable:!0,configurable:!0,set:Xe.bind(t,r)})};var le={};z(le,{DiscordDriver:()=>T,Driver:()=>T,configure:()=>_,default:()=>et,driver:()=>Qe,send:()=>V,sendEmbed:()=>Te,sendWebhook:()=>R});import{log as _e}from"@stacksjs/logging";import{log as Ye}from"@stacksjs/logging";class S{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 r=e instanceof Error?e:Error(String(e));Ye.error(`[${this.name}] Message sending failed`,{error:r.message,stack:r.stack?.replace(/https?:\/\/[^\s)\]"']+/g,"[REDACTED-URL]"),to:t.to,subject:t.subject});let s={message:`Message sending failed: ${r.message}`,success:!1,provider:this.name};if(t.onError){let n=t.onError(r),i=n instanceof Promise?await n:n;s={...s,...i,success:!1,provider:this.name}}return s}async handleSuccess(e,t){let r={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let s=e.handle(),n=s instanceof Promise?await s:s;r={...r,...n,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let s=e.onSuccess(),n=s instanceof Promise?await s:s;r={...r,...n,success:!0,provider:this.name,messageId:t}}}catch(s){return this.handleError(s,e)}return r}}var w={};function _(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.`)}w={...w,...e}}class T extends S{name="discord";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)_(e)}async send(e,t){let r={provider:this.name,to:e.to,subject:e.subject||"No subject"};_e.info("Sending message via Discord...",r);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(s){return this.handleError(s,e)}}async sendWithRetry(e,t,r=1){try{let s=await this.sendMessage(e,t);return this.handleSuccess(e,s.id)}catch(s){if(r<this.config.maxRetries)return _e.warn(`[${this.name}] Message send failed, retrying (${r}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,r+1);throw s}}async sendMessage(e,t){if(w.webhookUrl)return this.sendViaWebhook(e);if(w.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 r=await fetch(w.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){let n=await r.text();throw Error(`Discord webhook failed: ${r.status} - ${n}`)}if(r.status===204)return{};return{id:(await r.json()).id}}async sendViaBotToken(e){let t=e.to,r={content:e.content};if(e.subject||e.template)r.embeds=[this.buildEmbed(e)];let s=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${w.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok){let i=await s.json();throw Error(`Discord API failed: ${s.status} - ${i.message}`)}return{id:(await s.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 V(e,t){return new T().send(e,t)}async function R(e,t,r){try{let s={content:t,...r},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});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(s){return{success:!1,provider:"discord",message:(s instanceof Error?s:Error(String(s))).message}}}async function Te(e,t,r){return R(e,"",{...r,embeds:[t]})}var Qe=new T,et={send:V,sendWebhook:R,sendEmbed:Te,configure:_,DiscordDriver:T};var ce={};z(ce,{Driver:()=>D,SlackDriver:()=>D,configure:()=>I,default:()=>rt,driver:()=>tt,send:()=>K,sendWebhook:()=>q});import{log as Re}from"@stacksjs/logging";var E={};function I(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.`)}E={...E,...e}}class D extends S{name="slack";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)I(e)}async send(e,t){let r={provider:this.name,to:e.to,subject:e.subject||"No subject"};Re.info("Sending message via Slack...",r);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(s){return this.handleError(s,e)}}async sendWithRetry(e,t,r=1){try{let s=await this.sendMessage(e,t);return this.handleSuccess(e,s.ts)}catch(s){if(r<this.config.maxRetries)return Re.warn(`[${this.name}] Message send failed, retrying (${r}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,r+1);throw s}}async sendMessage(e,t){if(E.webhookUrl)return this.sendViaWebhook(e);if(E.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let r=e.content||e.subject||"",n={text:r.length>40000?`${r.slice(0,39986)}\u2026 [truncated]`:r,username:e.from?.name,mrkdwn:!0};if(e.template)n.blocks=this.buildBlocks(e);let i=await fetch(E.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 r=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${E.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw Error(`Slack API failed: ${r.status}`);let s=await r.json();if(!s.ok)throw Error(`Slack API error: ${s.error}`);return{ts:s.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 K(e,t){return new D().send(e,t)}async function q(e,t,r){try{let s={text:t,...r},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});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(s){return{success:!1,provider:"slack",message:(s instanceof Error?s:Error(String(s))).message}}}var tt=new D,rt={send:K,sendWebhook:q,configure:I,SlackDriver:D};var de={};z(de,{Driver:()=>O,TeamsDriver:()=>O,configure:()=>P,default:()=>nt,driver:()=>st,send:()=>X,sendCard:()=>De,sendWebhook:()=>Y});import{log as Ie}from"@stacksjs/logging";var ue={};function P(e){ue={...ue,...e}}class O extends S{name="teams";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)P(e)}async send(e,t){let r={provider:this.name,to:e.to,subject:e.subject||"No subject"};Ie.info("Sending message via Microsoft Teams...",r);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(s){return this.handleError(s,e)}}async sendWithRetry(e,t,r=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(s){if(r<this.config.maxRetries)return Ie.warn(`[${this.name}] Message send failed, retrying (${r}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,r+1);throw s}}async sendMessage(e,t){let s=ue.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!s||!s.includes("webhook.office.com"))throw Error("Teams webhook URL not configured or invalid");let n=this.buildPayload(e),i=await fetch(s,{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 X(e,t){return new O().send(e,t)}async function Y(e,t){try{let s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",text:t})});if(!s.ok){let n=await s.text();return{success:!1,provider:"teams",message:`Webhook failed: ${s.status} - ${n}`}}return{success:!0,provider:"teams",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"teams",message:(r instanceof Error?r:Error(String(r))).message}}}async function De(e,t,r){try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",summary:r||"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(s){return{success:!1,provider:"teams",message:(s instanceof Error?s:Error(String(s))).message}}}var st=new O,nt={send:X,sendWebhook:Y,sendCard:De,configure:P,TeamsDriver:O};var ze={};z(ze,{DiscordInboxDriver:()=>F,IMessageDriver:()=>W,SlackApiError:()=>ie,SlackInboxDriver:()=>B,WhatsAppDriver:()=>J,createInboxDriver:()=>zt,decodeAttributedBody:()=>te,formatHandle:()=>ee,formatJid:()=>ke,normalizeHandle:()=>x,snowflakeTime:()=>pe});function it(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 N(e){if(!e||e.length===0)return new Response("Not found",{status:404});return new Response(Uint8Array.from(e),{headers:{"content-type":it(e)}})}function Q(e,t){try{let r=new URL(e);return r.protocol==="https:"&&t.some((s)=>r.hostname===s||r.hostname.endsWith(`.${s}`))}catch{return!1}}var at=new Set([0,5]),me=new Set([10,11,12]);function pe(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}class F{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,r)=>fetch(t,r)),this.apiBase=(e.apiBase??"https://discord.com/api/v10").replace(/\/$/,""),this.concurrency=e.concurrency??3}async api(e,t={}){for(let r=0;;r++){let s=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{authorization:`Bot ${this.token}`,"content-type":"application/json",...t.headers??{}}});if(s.status===429&&r<3){let n=await s.json().catch(()=>({}));await Bun.sleep(Math.min(30,n.retry_after??1)*1000);continue}if(!s.ok){let n=await s.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${n.message??s.status}`)}return s.status===204?void 0:await s.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(me.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 s of e){this.guilds.set(s.id,s.name),this.icons.set(s.id,s.icon?`https://cdn.discordapp.com/icons/${s.id}/${s.icon}.png?size=128`:null);let[n,i]=await Promise.all([this.api(`/guilds/${s.id}/channels`).catch(()=>[]),this.api(`/guilds/${s.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...n.filter((a)=>at.has(a.type)).map((a)=>({...a,guild_id:s.id}))),t.push(...i.threads.map((a)=>({...a,guild_id:a.guild_id??s.id})))}let r=[];for(let s=0;s<t.length;s+=this.concurrency){let n=t.slice(s,s+this.concurrency);r.push(...await Promise.all(n.map((i)=>this.describe(i))))}return r.sort((s,n)=>(n.lastMessageAt??0)-(s.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,r=me.has(e.type);return{provider:"discord",id:e.id,kind:r?"thread":"channel",title:r?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):pe(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 r=t.limit??1000,s=[],n=(a,o)=>BigInt(a.id)<BigInt(o.id)?-1:1;if(t.after){let a=t.after;while(s.length<r){let o=await this.api(`/channels/${e}/messages?limit=100&after=${a}`);if(o.length===0)break;if(o.sort(n),s.push(...o),a=o[o.length-1].id,o.length<100)break}}else{let a="";while(s.length<r){let o=await this.api(`/channels/${e}/messages?limit=${Math.min(100,r-s.length)}${a?`&before=${a}`:""}`);if(o.length===0)break;if(o.sort(n),s.unshift(...o),a=o[0].id,o.length<100)break}}let i=[];for(let a of s.slice(0,r)){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 r=await this.api(`/channels/${e}`);return this.channels.set(e,r),r}async archive(e){let t=await this.channel(e),r=this.archiveSupport(t);if(r.mode!=="native")return{mode:r.mode,removed:!1,detail:r.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:r.detail}}async unarchive(e){let t=await this.channel(e);if(!me.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(!Q(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 se,readFileSync as St,statSync as Ct}from"fs";import{dirname as wt}from"path";import Et from"process";import{Database as dt}from"bun:sqlite";import{homedir as Le}from"os";import{join as $e}from"path";function Pe(e){if(e.length<40||new TextDecoder().decode(e.subarray(0,8))!=="bplist00")throw Error("Not a binary property list");let t=new DataView(e.buffer,e.byteOffset,e.byteLength),r=e.length-32,s=e[r+6],n=e[r+7],i=Number(t.getBigUint64(r+8)),a=Number(t.getBigUint64(r+16)),o=Number(t.getBigUint64(r+24)),l=(u,b)=>{let M=0;for(let p=0;p<b;p++)M=M*256+e[u+p];return M},g=(u)=>{if(u>=i)throw Error("Object reference out of range");return l(o+u*s,s)},c=new Set,m=(u)=>{if(c.has(u))throw Error("Cyclic property list");c.add(u);try{return v(g(u))}finally{c.delete(u)}},f=(u)=>{let b=e[u]&15;if(b!==15)return{length:b,start:u+1};let M=1<<(e[u+1]&15);return{length:l(u+2,M),start:u+2+M}},v=(u)=>{let b=e[u];switch(b>>4){case 0:return b===8?!1:b===9?!0:null;case 1:{let p=1<<(b&15);if(p===8)return t.getBigInt64(u+1);if(p===16)return t.getBigInt64(u+9);return l(u+1,p)}case 2:return(b&15)===2?t.getFloat32(u+1):t.getFloat64(u+1);case 3:return new Date(978307200000+t.getFloat64(u+1)*1000);case 4:{let{length:p,start:y}=f(u);return e.slice(y,y+p)}case 5:{let{length:p,start:y}=f(u);return new TextDecoder("latin1").decode(e.subarray(y,y+p))}case 6:{let{length:p,start:y}=f(u),C="";for(let A=0;A<p;A++)C+=String.fromCharCode(t.getUint16(y+A*2));return C}case 8:return{uid:l(u+1,(b&15)+1)};case 10:{let{length:p,start:y}=f(u);return Array.from({length:p},(C,A)=>m(l(y+A*n,n)))}case 13:{let{length:p,start:y}=f(u),C={};for(let A=0;A<p;A++){let Ve=m(l(y+A*n,n));C[String(Ve)]=m(l(y+(p+A)*n,n))}return C}default:throw Error(`Unsupported property list object 0x${b.toString(16)}`)}};return m(a)}function x(e){let t=e.trim();if(t.includes("@"))return t.toLowerCase();let r=t.replace(/[^\d+]/g,"");if(!/^\+?\d{7,15}$/.test(r))return t;if(r.startsWith("+"))return r;if(r.length===10)return`+1${r}`;return`+${r}`}function Oe(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function ee(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 Ne=new TextEncoder,ot=new TextDecoder("utf-8",{fatal:!1}),lt=[Ne.encode("NSString"),Ne.encode("NSMutableString")];function ct(e,t,r=0){e:for(let s=r;s<=e.length-t.length;s++){for(let n=0;n<t.length;n++)if(e[s+n]!==t[n])continue e;return s}return-1}function ut(e,t){let r=e[t];if(r===void 0)return null;let s=new DataView(e.buffer,e.byteOffset,e.byteLength);if(r===129){if(t+3>e.length)return null;return{length:s.getUint16(t+1,!0),start:t+3}}if(r===130){if(t+5>e.length)return null;return{length:s.getUint32(t+1,!0),start:t+5}}return{length:r,start:t+1}}function te(e){if(!e||e.length===0)return null;for(let t of lt){let r=ct(e,t);if(r===-1)continue;let s=r+t.length,n=Math.min(e.length,s+16);for(let i=s;i<n;i++){if(e[i]!==43)continue;let a=ut(e,i+1);if(!a||a.start+a.length>e.length)continue;return ot.decode(e.subarray(a.start,a.start+a.length))}}return null}var ge=$e(Le(),"Library","Messages","chat.db"),Ue=978307200,mt=45,L=43;function k(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+Ue)*1000)}function pt(e){return Math.round((e/1000-Ue)*1e9)}function Fe(e,t){let s=(e?.trim()?e:te(t))?.replace(/\uFFFC/g,"").trim();return s?s:null}function ht(e){if(!e||e.length===0)return null;try{let t=Pe(e),r=t&&typeof t==="object"&&!Array.isArray(t)&&!(t instanceof Uint8Array)&&!(t instanceof Date)?t.groupPhotoGuid:null;return typeof r==="string"&&r?r:null}catch{return null}}class re extends Error{path;needsFullDiskAccess;constructor(e,t){let r=t instanceof Error?t.message:String(t),s=/authorization denied|not permitted|unable to open/i.test(r);super(`Cannot read ${e}: ${r}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=s}}var gt=(e)=>e>=2000&&e<4000;function he(e){if(!e)return null;return e.startsWith("~/")?$e(Le(),e.slice(2)):e}function ft(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class fe{path;db;messageColumns;hasRecoverable;constructor(e=ge){this.path=e;try{this.db=new dt(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new re(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(),this.hasChatProperties=this.db.query("PRAGMA table_info(chat)").all().some((t)=>t.name==="properties")}hasChatProperties;attachmentFile(e){let t=this.db.query("SELECT filename FROM attachment WHERE guid = ?").get(e);return he(t?.filename??null)}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 lt=Object.defineProperty;var ct=(e)=>e;function ut(e,t){this[e]=ct.bind(null,t)}var te=(e,t)=>{for(var s in t)lt(e,s,{get:t[s],enumerable:!0,configurable:!0,set:ut.bind(t,s)})};var me={};te(me,{DiscordDriver:()=>P,Driver:()=>P,configure:()=>D,default:()=>ht,driver:()=>pt,send:()=>se,sendEmbed:()=>Fe,sendWebhook:()=>O});import{log as Ue}from"@stacksjs/logging";import{log as dt}from"@stacksjs/logging";class E{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));dt.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 n={message:`Message sending failed: ${s.message}`,success:!1,provider:this.name};if(t.onError){let r=t.onError(s),i=r instanceof Promise?await r:r;n={...n,...i,success:!1,provider:this.name}}return n}async handleSuccess(e,t){let s={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let n=e.handle(),r=n instanceof Promise?await n:n;s={...s,...r,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let n=e.onSuccess(),r=n instanceof Promise?await n:n;s={...s,...r,success:!0,provider:this.name,messageId:t}}}catch(n){return this.handleError(n,e)}return s}}var T={};function D(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.`)}T={...T,...e}}class P extends E{name="discord";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)D(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Ue.info("Sending message via Discord...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(n){return this.handleError(n,e)}}async sendWithRetry(e,t,s=1){try{let n=await this.sendMessage(e,t);return this.handleSuccess(e,n.id)}catch(n){if(s<this.config.maxRetries)return Ue.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((r)=>setTimeout(r,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw n}}async sendMessage(e,t){if(T.webhookUrl)return this.sendViaWebhook(e);if(T.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(T.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){let r=await s.text();throw Error(`Discord webhook failed: ${s.status} - ${r}`)}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 n=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${T.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!n.ok){let i=await n.json();throw Error(`Discord API failed: ${n.status} - ${i.message}`)}return{id:(await n.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 se(e,t){return new P().send(e,t)}async function O(e,t,s){try{let n={content:t,...s},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!r.ok){let i=await r.text();return{success:!1,provider:"discord",message:`Webhook failed: ${r.status} - ${i}`}}return{success:!0,provider:"discord",message:"Message sent successfully"}}catch(n){return{success:!1,provider:"discord",message:(n instanceof Error?n:Error(String(n))).message}}}async function Fe(e,t,s){return O(e,"",{...s,embeds:[t]})}var pt=new P,ht={send:se,sendWebhook:O,sendEmbed:Fe,configure:D,DiscordDriver:P};var ge={};te(ge,{Driver:()=>U,SlackDriver:()=>U,configure:()=>N,default:()=>gt,driver:()=>mt,send:()=>ne,sendWebhook:()=>re});import{log as Le}from"@stacksjs/logging";var R={};function N(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.`)}R={...R,...e}}class U extends E{name="slack";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)N(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Le.info("Sending message via Slack...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(n){return this.handleError(n,e)}}async sendWithRetry(e,t,s=1){try{let n=await this.sendMessage(e,t);return this.handleSuccess(e,n.ts)}catch(n){if(s<this.config.maxRetries)return Le.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((r)=>setTimeout(r,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw n}}async sendMessage(e,t){if(R.webhookUrl)return this.sendViaWebhook(e);if(R.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let s=e.content||e.subject||"",r={text:s.length>40000?`${s.slice(0,39986)}\u2026 [truncated]`:s,username:e.from?.name,mrkdwn:!0};if(e.template)r.blocks=this.buildBlocks(e);let i=await fetch(R.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});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 ${R.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok)throw Error(`Slack API failed: ${s.status}`);let n=await s.json();if(!n.ok)throw Error(`Slack API error: ${n.error}`);return{ts:n.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 ne(e,t){return new U().send(e,t)}async function re(e,t,s){try{let n={text:t,...s},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!r.ok){let i=await r.text();return{success:!1,provider:"slack",message:`Webhook failed: ${r.status} - ${i}`}}return{success:!0,provider:"slack",message:"Message sent successfully"}}catch(n){return{success:!1,provider:"slack",message:(n instanceof Error?n:Error(String(n))).message}}}var mt=new U,gt={send:ne,sendWebhook:re,configure:N,SlackDriver:U};var be={};te(be,{Driver:()=>L,TeamsDriver:()=>L,configure:()=>F,default:()=>bt,driver:()=>ft,send:()=>ie,sendCard:()=>We,sendWebhook:()=>ae});import{log as $e}from"@stacksjs/logging";var fe={};function F(e){fe={...fe,...e}}class L extends E{name="teams";constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(e)F(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};$e.info("Sending message via Microsoft Teams...",s);try{return this.validateMessage(e),await this.sendWithRetry(e,t)}catch(n){return this.handleError(n,e)}}async sendWithRetry(e,t,s=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(n){if(s<this.config.maxRetries)return $e.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((r)=>setTimeout(r,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw n}}async sendMessage(e,t){let n=fe.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!n||!n.includes("webhook.office.com"))throw Error("Teams webhook URL not configured or invalid");let r=this.buildPayload(e),i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});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 ie(e,t){return new L().send(e,t)}async function ae(e,t){try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"message",text:t})});if(!n.ok){let r=await n.text();return{success:!1,provider:"teams",message:`Webhook failed: ${n.status} - ${r}`}}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 We(e,t,s){try{let r=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(!r.ok){let i=await r.text();return{success:!1,provider:"teams",message:`Webhook failed: ${r.status} - ${i}`}}return{success:!0,provider:"teams",message:"Card sent successfully"}}catch(n){return{success:!1,provider:"teams",message:(n instanceof Error?n:Error(String(n))).message}}}var ft=new L,bt={send:ie,sendWebhook:ae,sendCard:We,configure:F,TeamsDriver:L};var at={};te(at,{DiscordInboxDriver:()=>Z,IMessageDriver:()=>z,SlackApiError:()=>V,SlackInboxDriver:()=>q,WhatsAppDriver:()=>ee,createInboxDriver:()=>ds,decodeAttributedBody:()=>ce,formatHandle:()=>le,formatJid:()=>Ne,normalizeHandle:()=>x,snowflakeTime:()=>Ae});var _={DISPATCH:0,HEARTBEAT:1,IDENTIFY:2,RESUME:6,RECONNECT:7,INVALID_SESSION:9,HELLO:10,HEARTBEAT_ACK:11},yt=new Set([4004,4010,4011,4012,4013,4014]);class ye{identity;options;socket=null;heartbeat=null;sequence=null;sessionId=null;resumeUrl=null;acknowledged=!0;stopped=!1;failures=0;Socket;url;readyResolvers=[];ready=null;constructor(e,t){this.identity=e;this.options=t;this.Socket=t.WebSocket??globalThis.WebSocket,this.url=t.url??"wss://gateway.discord.gg/?encoding=json&v=9"}whenReady(e=20000){if(this.ready)return Promise.resolve(this.ready);if(!this.socket&&!this.stopped)this.connect();return new Promise((t,s)=>{let n=setTimeout(()=>s(Error("Discord did not answer in time.")),e);this.readyResolvers.push((r)=>{clearTimeout(n),t(r)})})}connect(){if(this.stopped)return;let e=this.sessionId&&this.resumeUrl?`${this.resumeUrl}/?encoding=json&v=9`:this.url,t=new this.Socket(e);this.socket=t,t.onmessage=(s)=>this.onMessage(t,String(s.data)),t.onclose=(s)=>this.onClose(t,s?.code??0,s?.reason??"")}stop(){this.stopped=!0,this.clearHeartbeat(),this.socket?.close(),this.socket=null}send(e){this.socket?.send(JSON.stringify(e))}onMessage(e,t){if(e!==this.socket)return;let s;try{s=JSON.parse(t)}catch{return}if(s.s!=null)this.sequence=s.s;switch(s.op){case _.HELLO:if(this.startHeartbeat(s.d?.heartbeat_interval??41250),this.sessionId)this.send({op:_.RESUME,d:{token:this.identity.token,session_id:this.sessionId,seq:this.sequence}});else this.identify();break;case _.HEARTBEAT:this.send({op:_.HEARTBEAT,d:this.sequence});break;case _.HEARTBEAT_ACK:this.acknowledged=!0;break;case _.RECONNECT:e.close();break;case _.INVALID_SESSION:if(!s.d)this.sessionId=null,this.sequence=null;setTimeout(()=>this.sessionId?e.close():this.identify(),1000+Math.random()*4000);break;case _.DISPATCH:if(this.failures=0,s.t==="READY"){this.sessionId=s.d?.session_id??null,this.resumeUrl=s.d?.resume_gateway_url??null,this.ready=s.d;for(let n of this.readyResolvers.splice(0))n(s.d)}if(s.t)this.options.onDispatch(s.t,s.d);break}}identify(){this.send({op:_.IDENTIFY,d:{token:this.identity.token,capabilities:this.identity.capabilities??0,properties:this.identity.properties,presence:{status:"unknown",since:0,activities:[],afk:!1},compress:!1,client_state:{guild_versions:{}}}})}startHeartbeat(e){this.clearHeartbeat(),this.acknowledged=!0;let t=()=>{if(!this.acknowledged){this.socket?.close();return}this.acknowledged=!1,this.send({op:_.HEARTBEAT,d:this.sequence})};setTimeout(t,e*Math.random()),this.heartbeat=setInterval(t,e)}clearHeartbeat(){if(this.heartbeat)clearInterval(this.heartbeat);this.heartbeat=null}onClose(e,t,s){if(e!==this.socket)return;if(this.socket=null,this.clearHeartbeat(),this.stopped)return;if(yt.has(t)){this.stopped=!0,this.options.onAuthFailed?.(s||`closed with ${t}`);return}if(t===4007||t===4009)this.sessionId=null,this.sequence=null;let n=Math.min(60000,1000*2**Math.min(this.failures++,6));setTimeout(()=>this.connect(),n)}}var vt=1n<<10n,St=1n<<16n,At=1n<<3n,M=(e)=>BigInt(e??0);function je(e){if(e.ownerId&&e.ownerId===e.userId)return~0n;let t=new Map(e.roles.map((o)=>[o.id,o])),s=M(t.get(e.guildId)?.permissions);for(let o of e.memberRoles)s|=M(t.get(o)?.permissions);if(s&At)return~0n;let n=s,r=e.overwrites.find((o)=>o.id===e.guildId);if(r)n=n&~M(r.deny)|M(r.allow);let i=0n,a=0n;for(let o of e.overwrites)if((o.type===0||o.type==="role")&&e.memberRoles.includes(o.id))i|=M(o.allow),a|=M(o.deny);n=n&~a|i;let l=e.overwrites.find((o)=>(o.type===1||o.type==="member")&&o.id===e.userId);if(l)n=n&~M(l.deny)|M(l.allow);return n}function Be(e){return(e&vt)!==0n&&(e&St)!==0n}function wt(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 W(e){if(!e||e.length===0)return new Response("Not found",{status:404});return new Response(Uint8Array.from(e),{headers:{"content-type":wt(e)}})}function oe(e,t){try{let s=new URL(e);return s.protocol==="https:"&&t.some((n)=>s.hostname===n||s.hostname.endsWith(`.${n}`))}catch{return!1}}var Ze=new Set([0,5]),j=new Set([10,11,12]),y=1,A=3,ve={os:"Mac OS X",browser:"Discord Client",release_channel:"stable",client_version:"0.0.360",os_version:"25.0.0",os_arch:"arm64",app_arch:"arm64",system_locale:"en-US",browser_user_agent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) discord/0.0.360 Chrome/134.0.6998.205 Electron/35.3.0 Safari/537.36",browser_version:"35.3.0",client_build_number:420000,native_build_number:null,client_event_source:null};function Ae(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}var He=(e,t)=>!!e&&(!t||BigInt(e)>BigInt(t)),Se=(e)=>e.avatar?`https://cdn.discordapp.com/avatars/${e.id}/${e.avatar}.png?size=128`:null,B=(e)=>e.global_name||e.username;class Z{provider="discord";label="Discord";token;account;userId;fetch;apiBase;concurrency;Socket;gatewayUrl;session=null;listeners=new Set;guilds=new Map;icons=new Map;channels=new Map;users=new Map;readStates=new Map;muted=new Set;listed=new Set;constructor(e){this.token=e.token,this.account=e.account??"bot",this.userId=e.userId,this.fetch=e.fetch??((t,s)=>fetch(t,s)),this.apiBase=(e.apiBase??(this.account==="user"?"https://discord.com/api/v9":"https://discord.com/api/v10")).replace(/\/$/,""),this.concurrency=e.concurrency??3,this.Socket=e.WebSocket??globalThis.WebSocket,this.gatewayUrl=e.gatewayUrl}get isUser(){return this.account==="user"}headers(){if(!this.isUser)return{authorization:`Bot ${this.token}`};return{authorization:this.token,"user-agent":ve.browser_user_agent,"x-super-properties":btoa(JSON.stringify(ve)),"x-discord-locale":"en-US"}}async api(e,t={}){for(let s=0;;s++){let n=typeof t.body==="string",r=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{...this.headers(),...n?{"content-type":"application/json"}:{},...t.headers??{}}});if(r.status===429&&s<3){let i=await r.json().catch(()=>({}));await Bun.sleep(Math.min(30,i.retry_after??1)*1000);continue}if(!r.ok){let i=await r.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${i.message??r.status}`)}return r.status===204?void 0:await r.json()}}capabilities(){return{send:!0,attachments:!0,replies:!0,reactions:!0,markRead:this.isUser,live:this.isUser}}async status(){if(!this.token)return{connected:!1,detail:this.isUser?"Sign in with your Discord token.":"Add a Discord bot token.",needs:"token"};try{let e=await this.api("/users/@me");return this.userId??=e.id,{connected:!0,detail:this.isUser?`Signed in as ${B(e)}.`:`Reading the servers ${e.username} is in.`}}catch(e){return{connected:!1,detail:e instanceof Error?e.message:String(e),needs:"token"}}}gateway(){if(!this.session){if(!this.Socket)throw Error("No WebSocket to reach Discord with.");this.session=new ye({token:this.token,properties:ve,capabilities:0},{url:this.gatewayUrl,WebSocket:this.Socket,onDispatch:(e,t)=>this.onDispatch(e,t),onAuthFailed:(e)=>this.emit({type:"changed",conversationId:null},e)})}return this.session}emit(e,t){for(let s of this.listeners)s(e)}absorbReady(e){if(e.user)this.userId=e.user.id,this.users.set(e.user.id,e.user);for(let r of e.users??[])this.users.set(r.id,r);for(let r of e.private_channels??[]){for(let i of r.recipients??[])this.users.set(i.id,i);this.channels.set(r.id,r)}let t=Array.isArray(e.read_state)?e.read_state:e.read_state?.entries??[];for(let r of t)this.readStates.set(r.id,r);let s=Array.isArray(e.user_guild_settings)?e.user_guild_settings:e.user_guild_settings?.entries??[];for(let r of s){if(r.muted&&r.guild_id)this.muted.add(r.guild_id);for(let i of r.channel_overrides??[])if(i.muted)this.muted.add(i.channel_id)}(e.guilds??[]).forEach((r,i)=>{let a=r.properties?.name??r.name??"Server",l=r.properties?.icon??r.icon??null;this.guilds.set(r.id,a),this.icons.set(r.id,l?`https://cdn.discordapp.com/icons/${r.id}/${l}.png?size=128`:null);let o=e.merged_members?.[i]?.[0]?.roles??r.members?.find((c)=>c.user?.id===this.userId)?.roles??null,d=r.properties?.owner_id??r.owner_id??null;for(let c of[...r.channels??[],...r.threads??[]]){if(!Ze.has(c.type)&&!j.has(c.type))continue;let h={...c,guild_id:r.id};if(this.readStates.has(c.id)||o!==null&&Be(je({guildId:r.id,ownerId:d,userId:this.userId??"",memberRoles:o,roles:r.roles??[],overwrites:(j.has(c.type)?(r.channels??[]).find((b)=>b.id===c.parent_id)?.permission_overwrites:c.permission_overwrites)??[]})))this.channels.set(c.id,h)}})}onDispatch(e,t){switch(e){case"READY":this.absorbReady(t),this.emit({type:"changed",conversationId:null});break;case"MESSAGE_CREATE":{let s=this.channels.get(t.channel_id);if(s)this.channels.set(t.channel_id,{...s,last_message_id:t.id});if(t.author)this.users.set(t.author.id,t.author);let n=(t.mentions??[]).some((r)=>r.id===this.userId);if(this.listed.has(t.channel_id)||s?.type===y||s?.type===A||!s&&!t.guild_id||n)this.emit({type:"message",conversationId:t.channel_id,message:this.toMessage(t.channel_id,t)[0]});break}case"MESSAGE_ACK":this.readStates.set(t.channel_id,{id:t.channel_id,last_message_id:t.message_id,mention_count:t.mention_count??0}),this.emit({type:"changed",conversationId:t.channel_id});break;case"CHANNEL_CREATE":if(t.type===y||t.type===A){for(let s of t.recipients??[])this.users.set(s.id,s);this.channels.set(t.id,t),this.emit({type:"changed",conversationId:t.id})}break;case"CHANNEL_DELETE":if(t.type===y||t.type===A)this.channels.delete(t.id);this.emit({type:"changed",conversationId:t.id});break;case"MESSAGE_UPDATE":case"MESSAGE_DELETE":case"MESSAGE_REACTION_ADD":case"MESSAGE_REACTION_REMOVE":if(this.listed.has(t.channel_id)||this.channels.get(t.channel_id)?.type===y||this.channels.get(t.channel_id)?.type===A)this.emit({type:"changed",conversationId:t.channel_id});break}}watch(e){if(!this.isUser)return()=>{};return this.listeners.add(e),this.gateway().whenReady().catch(()=>{}),()=>{this.listeners.delete(e)}}close(){this.session?.stop(),this.session=null}archiveSupport(e){if(e.type===y)return{mode:"native",detail:"Closes the DM in Discord. It comes back when they write."};if(e.type===A)return{mode:"unsupported",detail:"Discord can only leave a group DM, which loses it, so it stays in Discord."};if(j.has(e.type)&&!this.isUser)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(){if(this.isUser)return this.userConversations();let e=await this.api("/users/@me/guilds"),t=[];for(let n of e){this.guilds.set(n.id,n.name),this.icons.set(n.id,n.icon?`https://cdn.discordapp.com/icons/${n.id}/${n.icon}.png?size=128`:null);let[r,i]=await Promise.all([this.api(`/guilds/${n.id}/channels`).catch(()=>[]),this.api(`/guilds/${n.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...r.filter((a)=>Ze.has(a.type)).map((a)=>({...a,guild_id:n.id}))),t.push(...i.threads.map((a)=>({...a,guild_id:a.guild_id??n.id})))}let s=[];for(let n=0;n<t.length;n+=this.concurrency){let r=t.slice(n,n+this.concurrency);s.push(...await Promise.all(r.map((i)=>this.describe(i,!0))))}return s.sort((n,r)=>(r.lastMessageAt??0)-(n.lastMessageAt??0))}async userConversations(){if(!this.session?.ready)this.absorbReady(await this.gateway().whenReady());let e=[...this.channels.values()].filter((n)=>n.type===y||n.type===A||this.readStates.has(n.id)).sort((n,r)=>He(n.last_message_id,r.last_message_id)?-1:1),t=new Set(e.filter((n)=>n.type===y||n.type===A).slice(0,40).map((n)=>n.id)),s=[];for(let n=0;n<e.length;n+=this.concurrency){let r=e.slice(n,n+this.concurrency);s.push(...await Promise.all(r.map((i)=>this.describe(i,t.has(i.id)))))}return this.listed=new Set(s.map((n)=>n.id)),s}recipients(e){if(e.recipients?.length)return e.recipients;return(e.recipient_ids??[]).map((t)=>this.users.get(t)).filter((t)=>!!t)}async describe(e,t){this.channels.set(e.id,e);let s=t&&e.last_message_id?(await this.api(`/channels/${e.id}/messages?limit=1`).catch(()=>[]))[0]:void 0,n=j.has(e.type),r=e.type===y||e.type===A,i=r?this.recipients(e):[],a=e.type===y?i[0]?B(i[0]):"Direct message":e.type===A?e.name||i.map(B).join(", ")||"Group":n?e.name??"Thread":`#${e.name}`,l=this.readStates.get(e.id),o=this.muted.has(e.id)||!!e.guild_id&&this.muted.has(e.guild_id),d=this.isUser&&He(e.last_message_id,l?.last_message_id??(r?null:e.last_message_id)),c=!this.isUser?0:l?.mention_count||(d&&(r||!o)?1:0),h=e.type===y?i[0]?Se(i[0]):null:e.type===A?e.icon?`https://cdn.discordapp.com/channel-icons/${e.id}/${e.icon}.png?size=128`:null:e.guild_id?this.icons.get(e.guild_id)??null:null;return{provider:"discord",id:e.id,kind:e.type===y?"direct":e.type===A?"group":n?"thread":"channel",title:a,participants:i.map((m)=>({id:m.id,name:B(m),avatar:Se(m)})),workspace:e.guild_id?this.guilds.get(e.guild_id)??null:null,avatar:h,lastMessageAt:s?Date.parse(s.timestamp):Ae(e.last_message_id),preview:s?s.content||(s.attachments?.length?"Attachment":null):null,previewFromMe:!!s&&s.author.id===this.userId,unread:c,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,n=[],r=(i,a)=>BigInt(i.id)<BigInt(a.id)?-1:1;if(t.after){let i=t.after;while(n.length<s){let a=await this.api(`/channels/${e}/messages?limit=100&after=${i}`);if(a.length===0)break;if(a.sort(r),n.push(...a),i=a[a.length-1].id,a.length<100)break}}else{let i="";while(n.length<s){let a=await this.api(`/channels/${e}/messages?limit=${Math.min(100,s-n.length)}${i?`&before=${i}`:""}`);if(a.length===0)break;if(a.sort(r),n.unshift(...a),i=a[0].id,a.length<100)break}}return n.slice(0,s).flatMap((i)=>this.toMessage(e,i))}toMessage(e,t){let s=t.author.id===this.userId,n=[{id:t.id,conversationId:e,kind:[0,19,20,21].includes(t.type)?"message":"event",text:t.content||null,fromMe:s,sentAt:Date.parse(t.timestamp),sender:s?null:{id:t.author.id,name:B(t.author),avatar:Se(t.author)},targetId:null,reaction:null,reactionRemoved:!1,replyToId:t.message_reference?.message_id??null,editedAt:t.edited_timestamp?Date.parse(t.edited_timestamp):null,unsent:!1,attachments:(t.attachments??[]).map((r)=>({id:r.id,name:r.filename,mimeType:r.content_type??null,bytes:r.size,path:null,url:r.url})),cursor:t.id}];for(let r of t.reactions??[])n.push({id:`${t.id}:${r.emoji.id??r.emoji.name}`,conversationId:e,kind:"reaction",text:r.count>1?String(r.count):null,fromMe:!!r.me,sentAt:Date.parse(t.timestamp),sender:null,targetId:t.id,reaction:r.emoji.name??"\u2022",reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:t.id});return n}async send(e,t){let s=t.text?.trim()??"",n=t.files??[];if(!s&&n.length===0)throw Error("Nothing to send.");let r=String(BigInt(Date.now())-1420070400000n<<22n),i={content:s,nonce:r,tts:!1};if(t.replyToId)i.message_reference={message_id:t.replyToId,channel_id:e};let a;if(n.length===0)a=await this.api(`/channels/${e}/messages`,{method:"POST",body:JSON.stringify(i)});else{let o=new FormData;i.attachments=n.map((d,c)=>({id:c,filename:d.name??d.path.split("/").pop()})),o.append("payload_json",JSON.stringify(i));for(let[d,c]of n.entries())o.append(`files[${d}]`,Bun.file(c.path),c.name??c.path.split("/").pop());a=await this.api(`/channels/${e}/messages`,{method:"POST",body:o})}let l=this.channels.get(e);if(l)this.channels.set(e,{...l,last_message_id:a.id});return{id:a.id,sentAt:Date.parse(a.timestamp)||Date.now()}}async react(e,t,s,n=!1){let r=t.split(":")[0];await this.api(`/channels/${e}/messages/${r}/reactions/${encodeURIComponent(s)}/@me`,{method:n?"DELETE":"PUT"})}async markRead(e){if(!this.isUser)return;let t=this.channels.get(e)?.last_message_id;if(!t)t=(await this.api(`/channels/${e}/messages?limit=1`))[0]?.id;if(!t)return;await this.api(`/channels/${e}/messages/${t}/ack`,{method:"POST",body:JSON.stringify({token:null})}),this.readStates.set(e,{id:e,last_message_id:t,mention_count:0})}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};if(t.type===y)return await this.api(`/channels/${e}`,{method:"DELETE"}),{mode:"native",removed:!0,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(t.type===y){let s=this.recipients(t)[0];if(s)await this.api("/users/@me/channels",{method:"POST",body:JSON.stringify({recipients:[s.id]})});return}if(!j.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(!oe(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 de,readFileSync as $t,statSync as Wt}from"fs";import{dirname as jt}from"path";import Bt from"process";import{Database as Et}from"bun:sqlite";import{homedir as Ve}from"os";import{join as qe}from"path";function Ge(e){if(e.length<40||new TextDecoder().decode(e.subarray(0,8))!=="bplist00")throw Error("Not a binary property list");let t=new DataView(e.buffer,e.byteOffset,e.byteLength),s=e.length-32,n=e[s+6],r=e[s+7],i=Number(t.getBigUint64(s+8)),a=Number(t.getBigUint64(s+16)),l=Number(t.getBigUint64(s+24)),o=(u,v)=>{let k=0;for(let g=0;g<v;g++)k=k*256+e[u+g];return k},d=(u)=>{if(u>=i)throw Error("Object reference out of range");return o(l+u*n,n)},c=new Set,h=(u)=>{if(c.has(u))throw Error("Cyclic property list");c.add(u);try{return b(d(u))}finally{c.delete(u)}},m=(u)=>{let v=e[u]&15;if(v!==15)return{length:v,start:u+1};let k=1<<(e[u+1]&15);return{length:o(u+2,k),start:u+2+k}},b=(u)=>{let v=e[u];switch(v>>4){case 0:return v===8?!1:v===9?!0:null;case 1:{let g=1<<(v&15);if(g===8)return t.getBigInt64(u+1);if(g===16)return t.getBigInt64(u+9);return o(u+1,g)}case 2:return(v&15)===2?t.getFloat32(u+1):t.getFloat64(u+1);case 3:return new Date(978307200000+t.getFloat64(u+1)*1000);case 4:{let{length:g,start:S}=m(u);return e.slice(S,S+g)}case 5:{let{length:g,start:S}=m(u);return new TextDecoder("latin1").decode(e.subarray(S,S+g))}case 6:{let{length:g,start:S}=m(u),C="";for(let w=0;w<g;w++)C+=String.fromCharCode(t.getUint16(S+w*2));return C}case 8:return{uid:o(u+1,(v&15)+1)};case 10:{let{length:g,start:S}=m(u);return Array.from({length:g},(C,w)=>h(o(S+w*r,r)))}case 13:{let{length:g,start:S}=m(u),C={};for(let w=0;w<g;w++){let ot=h(o(S+w*r,r));C[String(ot)]=h(o(S+(g+w)*r,r))}return C}default:throw Error(`Unsupported property list object 0x${v.toString(16)}`)}};return h(a)}function x(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 Je(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function le(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 ze=new TextEncoder,_t=new TextDecoder("utf-8",{fatal:!1}),xt=[ze.encode("NSString"),ze.encode("NSMutableString")];function Mt(e,t,s=0){e:for(let n=s;n<=e.length-t.length;n++){for(let r=0;r<t.length;r++)if(e[n+r]!==t[r])continue e;return n}return-1}function kt(e,t){let s=e[t];if(s===void 0)return null;let n=new DataView(e.buffer,e.byteOffset,e.byteLength);if(s===129){if(t+3>e.length)return null;return{length:n.getUint16(t+1,!0),start:t+3}}if(s===130){if(t+5>e.length)return null;return{length:n.getUint32(t+1,!0),start:t+5}}return{length:s,start:t+1}}function ce(e){if(!e||e.length===0)return null;for(let t of xt){let s=Mt(e,t);if(s===-1)continue;let n=s+t.length,r=Math.min(e.length,n+16);for(let i=n;i<r;i++){if(e[i]!==43)continue;let a=kt(e,i+1);if(!a||a.start+a.length>e.length)continue;return _t.decode(e.subarray(a.start,a.start+a.length))}}return null}var _e=qe(Ve(),"Library","Messages","chat.db"),Ye=978307200,Ct=45,H=43;function I(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+Ye)*1000)}function Tt(e){return Math.round((e/1000-Ye)*1e9)}function Ke(e,t){let n=(e?.trim()?e:ce(t))?.replace(/\uFFFC/g,"").trim();return n?n:null}function Rt(e){if(!e||e.length===0)return null;try{let t=Ge(e),s=t&&typeof t==="object"&&!Array.isArray(t)&&!(t instanceof Uint8Array)&&!(t instanceof Date)?t.groupPhotoGuid:null;return typeof s==="string"&&s?s:null}catch{return null}}class ue extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t),n=/authorization denied|not permitted|unable to open/i.test(s);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=n}}var It=(e)=>e>=2000&&e<4000;function we(e){if(!e)return null;return e.startsWith("~/")?qe(Ve(),e.slice(2)):e}function Dt(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class xe{path;db;messageColumns;hasRecoverable;constructor(e=_e){this.path=e;try{this.db=new Et(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new ue(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(),this.hasChatProperties=this.db.query("PRAGMA table_info(chat)").all().some((t)=>t.name==="properties")}hasChatProperties;attachmentFile(e){let t=this.db.query("SELECT filename FROM attachment WHERE guid = ?").get(e);return we(t?.filename??null)}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
  ${this.hasChatProperties?"c.properties":"NULL"} AS properties,
@@ -10,9 +10,9 @@ var Ke=Object.defineProperty;var qe=(e)=>e;function Xe(e,t){this[e]=qe.bind(null
10
10
  AND (m.associated_message_type IS NULL OR m.associated_message_type = 0)) AS unread_count,
11
11
  (SELECT MAX(j.message_id) FROM chat_message_join j WHERE j.chat_id = c.ROWID) AS last_rowid
12
12
  FROM chat c
13
- `).all(),r=this.participantsByChat(),s=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?s.get(i.last_rowid):null;return{rowid:i.rowid,guid:i.guid,identifier:i.chat_identifier?x(i.chat_identifier):i.guid,style:i.style??mt,service:i.service_name??"iMessage",displayName:i.display_name?.trim()||null,groupId:i.group_id||null,groupPhotoGuid:ht(i.properties),participants:r.get(i.rowid)??[],messageCount:i.message_count,recoverableCount:i.recoverable_count,unreadCount:i.unread_count,lastMessageAt:a?k(a.date):null,lastMessageRowId:i.last_rowid??0,lastMessageText:a?Fe(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
+ `).all(),s=this.participantsByChat(),n=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?n.get(i.last_rowid):null;return{rowid:i.rowid,guid:i.guid,identifier:i.chat_identifier?x(i.chat_identifier):i.guid,style:i.style??Ct,service:i.service_name??"iMessage",displayName:i.display_name?.trim()||null,groupId:i.group_id||null,groupPhotoGuid:Rt(i.properties),participants:s.get(i.rowid)??[],messageCount:i.message_count,recoverableCount:i.recoverable_count,unreadCount:i.unread_count,lastMessageAt:a?I(a.date):null,lastMessageRowId:i.last_rowid??0,lastMessageText:a?Ke(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(`
14
14
  SELECT chj.chat_id, h.id FROM chat_handle_join chj JOIN handle h ON h.ROWID = chj.handle_id
15
- `).all(),t=new Map;for(let r of e){let s=t.get(r.chat_id)??[],n=x(r.id);if(!s.includes(n))s.push(n);t.set(r.chat_id,s)}return t}messages(e,t={}){if(e.length===0)return[];let r=e.map(()=>"?").join(", "),s=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 < ${pt(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
+ `).all(),t=new Map;for(let s of e){let n=t.get(s.chat_id)??[],r=x(s.id);if(!n.includes(r))n.push(r);t.set(s.chat_id,n)}return t}messages(e,t={}){if(e.length===0)return[];let s=e.map(()=>"?").join(", "),n=t.newestFirst?"m.date DESC, m.ROWID DESC":t.byRowId?"m.ROWID ASC":"m.date ASC, m.ROWID ASC",r=t.beforeMs?`AND m.date < ${Tt(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(`
16
16
  SELECT
17
17
  m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.is_from_me, m.date, m.service,
18
18
  m.associated_message_guid, m.associated_message_type, m.item_type, m.group_title,
@@ -26,17 +26,27 @@ var Ke=Object.defineProperty;var qe=(e)=>e;function Xe(e,t){this[e]=qe.bind(null
26
26
  JOIN ${i} cmj ON cmj.message_id = m.ROWID
27
27
  JOIN chat c ON c.ROWID = cmj.chat_id
28
28
  LEFT JOIN handle h ON h.ROWID = m.handle_id
29
- WHERE c.guid IN (${r}) AND m.ROWID > ? ${n}
30
- ORDER BY ${s}
29
+ WHERE c.guid IN (${s}) AND m.ROWID > ? ${r}
30
+ ORDER BY ${n}
31
31
  LIMIT ?
32
- `).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=gt(g)?"reaction":c!==0?"event":"message";return{rowid:l.rowid,guid:l.guid,chatGuid:l.chat_guid,kind:m,text:Fe(l.text,l.attributedBody),isFromMe:l.is_from_me===1,sentAt:k(l.date)??0,sender:l.sender?x(l.sender):null,service:l.service??"iMessage",targetGuid:m==="reaction"?ft(l.associated_message_guid):null,reactionType:m==="reaction"?g:0,replyToGuid:l.thread_originator_guid||null,editedAt:k(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 r=he(t.filename);return{path:r,mimeType:t.mime_type,name:t.transfer_name||(r?r.split("/").pop():e)}}attachmentsFor(e){let t=new Map;for(let r=0;r<e.length;r+=500){let s=e.slice(r,r+500),n=this.db.query(`
32
+ `).all(...e,t.afterRowId??0,t.limit??1e6),l=this.attachmentsFor(a.filter((o)=>o.cache_has_attachments===1).map((o)=>o.rowid));return a.map((o)=>{let d=o.associated_message_type??0,c=o.item_type??0,h=It(d)?"reaction":c!==0?"event":"message";return{rowid:o.rowid,guid:o.guid,chatGuid:o.chat_guid,kind:h,text:Ke(o.text,o.attributedBody),isFromMe:o.is_from_me===1,sentAt:I(o.date)??0,sender:o.sender?x(o.sender):null,service:o.service??"iMessage",targetGuid:h==="reaction"?Dt(o.associated_message_guid):null,reactionType:h==="reaction"?d:0,replyToGuid:o.thread_originator_guid||null,editedAt:I(o.date_edited),unsent:!!o.date_retracted,eventType:c,groupTitle:o.group_title||null,attachments:l.get(o.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=we(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 n=e.slice(s,s+500),r=this.db.query(`
33
33
  SELECT maj.message_id, a.guid, a.filename, a.transfer_name, a.mime_type, a.uti, a.total_bytes, a.is_sticker
34
34
  FROM message_attachment_join maj JOIN attachment a ON a.ROWID = maj.attachment_id
35
- WHERE maj.message_id IN (${s.map(()=>"?").join(", ")})
36
- `).all(...s);for(let i of n){let a=t.get(i.message_id)??[],o=he(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 je}from"bun:sqlite";import{existsSync as We,readdirSync as bt,readFileSync as yt}from"fs";import{homedir as vt}from"os";import{dirname as At,join as U}from"path";var be=U(vt(),"Library","Application Support","AddressBook");function Be(e,t){if(!t||t.length<2)return null;if(t[0]===1)return t.subarray(1);if(t[0]===2){let r=new TextDecoder().decode(t.subarray(1)).replace(/\0+$/,"").trim();if(!/^[\w-]+$/.test(r))return null;let s=U(At(e),".AddressBook-v22_SUPPORT","_EXTERNAL_DATA",r);try{return new Uint8Array(yt(s))}catch{return null}}return null}function xt(e){let t=[],r=U(e,"AddressBook-v22.abcddb");if(We(r))t.push(r);let s=U(e,"Sources");try{for(let n of bt(s)){let i=U(s,n,"AddressBook-v22.abcddb");if(We(i))t.push(i)}}catch{}return t}function Ze(e){let t=e.replace(/[^\p{L}\p{N}\s]/gu," ").trim().split(/\s+/).filter(Boolean);if(t.length===0)return"?";let r=t[0][0],s=t.length>1?t[t.length-1][0]:"";return(r+s).toUpperCase()}class j{byHandle=new Map;static load(e=be){let t=new j;for(let r of xt(e))try{t.read(r)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new je(e,{readonly:!0});try{let r=new Set(t.query("PRAGMA table_info(ZABCDRECORD)").all().map((c)=>c.name)),s=["ZTHUMBNAILIMAGEDATA","ZIMAGEDATA"].filter((c)=>r.has(c)),n=s.length>0?`(${s.map((c)=>`${c} IS NOT NULL`).join(" OR ")})`:"0",i=t.query(`
37
- SELECT Z_PK AS id, ZFIRSTNAME AS first, ZLASTNAME AS last, ZNICKNAME AS nick, ZORGANIZATION AS org, ${n} AS hasPhoto
35
+ WHERE maj.message_id IN (${n.map(()=>"?").join(", ")})
36
+ `).all(...n);for(let i of r){let a=t.get(i.message_id)??[],l=we(i.filename);a.push({guid:i.guid,path:l,name:i.transfer_name||(l?l.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 Xe}from"bun:sqlite";import{existsSync as Qe,readdirSync as Pt,readFileSync as Ot}from"fs";import{homedir as Nt}from"os";import{dirname as Ut,join as G}from"path";var Me=G(Nt(),"Library","Application Support","AddressBook");function et(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 n=G(Ut(e),".AddressBook-v22_SUPPORT","_EXTERNAL_DATA",s);try{return new Uint8Array(Ot(n))}catch{return null}}return null}function Ft(e){let t=[],s=G(e,"AddressBook-v22.abcddb");if(Qe(s))t.push(s);let n=G(e,"Sources");try{for(let r of Pt(n)){let i=G(n,r,"AddressBook-v22.abcddb");if(Qe(i))t.push(i)}}catch{}return t}function tt(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],n=t.length>1?t[t.length-1][0]:"";return(s+n).toUpperCase()}class J{byHandle=new Map;static load(e=Me){let t=new J;for(let s of Ft(e))try{t.read(s)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new Xe(e,{readonly:!0});try{let s=new Set(t.query("PRAGMA table_info(ZABCDRECORD)").all().map((c)=>c.name)),n=["ZTHUMBNAILIMAGEDATA","ZIMAGEDATA"].filter((c)=>s.has(c)),r=n.length>0?`(${n.map((c)=>`${c} IS NOT NULL`).join(" OR ")})`:"0",i=t.query(`
37
+ SELECT Z_PK AS id, ZFIRSTNAME AS first, ZLASTNAME AS last, ZNICKNAME AS nick, ZORGANIZATION AS org, ${r} AS hasPhoto
38
38
  FROM ZABCDRECORD
39
- `).all(),a=new Map,o=new Set;for(let c of i){let f=[c.first,c.last].filter(Boolean).join(" ").trim()||c.nick?.trim()||c.org?.trim();if(f)a.set(c.id,f);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 f=x(c.value),v=o.has(c.owner)?{file:e,record:c.owner}:null,u=this.byHandle.get(f);if(!u)this.byHandle.set(f,{name:m,initials:Ze(m),photo:v});else if(!u.photo&&v)u.photo=v}}finally{t.close()}}set(e,t){this.byHandle.set(x(e),{name:t,initials:Ze(t)})}lookup(e){return this.byHandle.get(x(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 r=new je(t.file,{readonly:!0});try{let s=r.query("SELECT ZTHUMBNAILIMAGEDATA AS thumb, ZIMAGEDATA AS full FROM ZABCDRECORD WHERE Z_PK = ?").get(t.record);return Be(t.file,s?.thumb??null)??Be(t.file,s?.full??null)}finally{r.close()}}catch{return null}}}function Mt(e){if(e.style===L)return`group:${e.groupId||e.guid}`;return`direct:${e.identifier}`}function He(e){let t=new Map;for(let r of e){let s=Mt(r),n=t.get(s);if(!n){t.set(s,{key:s,kind:r.style===L?"group":"direct",chatGuids:[r.guid],displayName:r.displayName,groupId:r.groupId,groupPhotoGuid:r.style===L?r.groupPhotoGuid:null,participants:r.style===L?[...r.participants]:[r.identifier],messageCount:r.messageCount,recoverableCount:r.recoverableCount,unreadCount:r.unreadCount,lastMessageAt:r.lastMessageAt,lastMessageRowId:r.lastMessageRowId,lastMessageText:r.lastMessageText,lastMessageFromMe:r.lastMessageFromMe,services:[r.service]});continue}if(n.chatGuids.push(r.guid),n.kind==="group"&&!n.groupPhotoGuid)n.groupPhotoGuid=r.groupPhotoGuid;if(n.messageCount+=r.messageCount,n.recoverableCount+=r.recoverableCount,n.unreadCount+=r.unreadCount,!n.services.includes(r.service))n.services.push(r.service);for(let i of r.participants)if(n.kind==="group"&&!n.participants.includes(i))n.participants.push(i);if((r.lastMessageAt??0)>(n.lastMessageAt??0))n.lastMessageAt=r.lastMessageAt,n.lastMessageRowId=r.lastMessageRowId,n.lastMessageText=r.lastMessageText,n.lastMessageFromMe=r.lastMessageFromMe,n.displayName=r.displayName??n.displayName}return[...t.values()].sort((r,s)=>(s.lastMessageAt??0)-(r.lastMessageAt??0))}function ye(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!Oe(t))return null;return`sms:${t}`}var kt={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function _t(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??ge,this.addressBookDir=e.addressBookDir??be,this.openUrl=e.openUrl??_t,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??Et.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]),ee(e.participants[0]),e.participants[0]);return[...new Set(t.filter((r)=>!!r))]}open(e){let t=new fe(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new j;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=j.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){if(e.startsWith("group:")){let t=this.open((r)=>r.attachmentFile(e.slice(6)));return N(t&&se(t)?new Uint8Array(St(t)):null)}return N(this.names().photo(e))}grouped=null;chats(e){let t=[this.databasePath,`${this.databasePath}-wal`].map((r)=>{try{let s=Ct(r);return`${s.mtimeMs}:${s.size}`}catch{return"-"}}).join("|");if(this.grouped?.stamp!==t)this.grouped={stamp:t,chats:He(e.chats())};return this.grouped.chats}find(e,t){return this.chats(e).find((r)=>r.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 re&&e.needsFullDiskAccess&&se(wt(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)=>this.chats(e).map((t)=>this.toConversation(t)))}toConversation(e){let t=ye(e);return{provider:"imessage",id:e.key,kind:e.kind,title:e.kind==="group"?e.displayName:null,participants:e.participants.map((r)=>this.person(r)),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,avatar:e.kind==="group"&&e.groupPhotoGuid?`group:${e.groupPhotoGuid}`:null}}async messages(e,t={}){return this.open((r)=>{let s=this.find(r,e);if(!s)return[];let n=t.includeDeleted;return(t.after||!t.limit?r.messages(s.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:n}):r.messages(s.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:n}).reverse()).map((a)=>this.toMessage(e,a))})}toMessage(e,t){let r=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"?kt[r?t.reactionType-1000:t.reactionType]??"\u2022":null,reactionRemoved:r,replyToId:t.replyToGuid,service:t.service,editedAt:t.editedAt,unsent:t.unsent,attachments:t.attachments.map((s)=>({id:s.guid,name:s.name,mimeType:s.mimeType,bytes:s.bytes,path:s.path&&se(s.path)?s.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((n)=>this.find(n,e)),r=t?ye(t):null;if(!t||!r)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 s=this.rowNames(t);if(this.controller&&s.length>0){let n=await this.controller.remove(s,r,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(r),{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((s)=>this.find(s,e));if(!t||t.messageCount>0)return;let r=this.rowNames(t);if(r.length>0)await this.controller.recover(r)}async attachment(e){if(!e.path||!se(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 Tt="im,mpim,public_channel,private_channel",Rt={"+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"},ne=(e)=>Math.round(Number(e)*1000);class ie extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class B{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,r)=>fetch(t,r)),this.apiBase=(e.apiBase??"https://slack.com/api").replace(/\/$/,""),this.concurrency=e.concurrency??4}async api(e,t={},r=!1){for(let s=0;;s++){let n=new URLSearchParams(Object.entries(t).map(([o,l])=>[o,String(l)])),i=r?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&&s<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 ie(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(!Q(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 r=await this.api("users.conversations",{types:Tt,exclude_archived:!0,limit:200,...t?{cursor:t}:{}});e.push(...r.channels),t=r.response_metadata?.next_cursor??""}while(t);return e}async conversations(){let e=await this.whoami(),t=await this.list(),r=[];for(let s=0;s<t.length;s+=this.concurrency){let n=t.slice(s,s+this.concurrency);r.push(...await Promise.all(n.map((i)=>this.describe(i,e))))}return r.sort((s,n)=>(n.lastMessageAt??0)-(s.lastMessageAt??0))}async describe(e,t){let[{channel:r},s]=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,...r};this.channels.set(n.id,n);let i=s.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?ne(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 r=await this.whoami(),s=[],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}:{}});s.push(...o.messages),n=o.has_more?o.response_metadata?.next_cursor??"":""}while(n&&s.length<i);s.sort((o,l)=>Number(o.ts)-Number(l.ts));let a=[];for(let o of s.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===r.userId,sentAt:ne(o.ts),sender:o.user===r.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?ne(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===r.userId,sentAt:ne(o.ts),sender:m===r.userId?null:await this.person(m),targetId:o.ts,reaction:Rt[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:r}=await this.api("conversations.info",{channel:e});return this.channels.set(e,r),r}async archive(e){let t=await this.channel(e),r=this.archiveSupport(t);if(r.mode!=="native")return{mode:r.mode,removed:!1,detail:r.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:r.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 oe,readFileSync as Lt}from"fs";import{basename as $t,dirname as we,extname as Ut,join as Ee}from"path";import jt from"process";function ve(e,t){let r=0n,s=0n;for(let n=t;n<e.length&&n<t+10;n++){let i=e[n];if(r|=BigInt(i&127)<<s,(i&128)===0)return{value:r,next:n+1};s+=7n}throw Error("Truncated varint")}function It(e){let t=[],r=0;while(r<e.length){let s=ve(e,r);r=s.next;let n=Number(s.value>>3n),i=Number(s.value&7n);if(n===0)throw Error("Field number 0");if(i===0){let a=ve(e,r);t.push({field:n,wire:i,value:a.value}),r=a.next}else if(i===1||i===5){let a=i===1?8:4;if(r+a>e.length)throw Error("Truncated fixed field");let o=0n;for(let l=a-1;l>=0;l--)o=o<<8n|BigInt(e[r+l]);t.push({field:n,wire:i,value:o}),r+=a}else if(i===2){let a=ve(e,r),o=a.next,l=o+Number(a.value);if(l>e.length)throw Error("Truncated length-delimited field");t.push({field:n,wire:i,value:e.subarray(o,l)}),r=l}else throw Error(`Unsupported wire type ${i}`)}return t}function Z(e){if(!e||e.length===0)return null;try{return It(e)}catch{return null}}function Dt(e,t){let r=e?.find((s)=>s.field===t&&s.wire===2);return r?r.value:null}function Ae(e,t){return(e??[]).filter((r)=>r.field===t&&r.wire===2).map((r)=>r.value)}function H(e,t){let r=Dt(e,t);if(!r)return null;let s=new TextDecoder("utf-8",{fatal:!0});try{return s.decode(r)}catch{return null}}function xe(e,t){let r=e?.find((s)=>s.field===t&&s.wire!==2);return r?r.value:null}import{Database as Pt}from"bun:sqlite";import{homedir as Ot}from"os";import{join as Me}from"path";var Ge=Me(Ot(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),Se=Me(Ge,"ChatStorage.sqlite"),Je=Me(Ge,"Message"),G=0,Nt=1,Ft=4,d={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 ae extends Error{path;needsFullDiskAccess;constructor(e,t){let r=t instanceof Error?t.message:String(t);super(`Cannot read ${e}: ${r}`);this.path=e;this.name="WhatsAppAccessError",this.needsFullDiskAccess=/authorization denied|not permitted|unable to open/i.test(r)}}function h(e){let t=e?.replace(/\uFFFC/g,"").trim();return t?t:null}class Ce{path;db;constructor(e=Se){this.path=e;try{this.db=new Pt(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new ae(e,t)}}close(){this.db.close()}columns(e){try{return new Set(this.db.query(`PRAGMA table_info(${e})`).all().map((t)=>t.name))}catch{return new Set}}get hasMetadata(){return this.columns("ZWAMEDIAITEM").has("ZMETADATA")}get hasReceipts(){return this.columns("ZWAMESSAGE").has("ZMESSAGEINFO")&&this.columns("ZWAMESSAGEINFO").has("ZRECEIPTINFO")}sessions(e){return this.db.query(`
39
+ `).all(),a=new Map,l=new Set;for(let c of i){let m=[c.first,c.last].filter(Boolean).join(" ").trim()||c.nick?.trim()||c.org?.trim();if(m)a.set(c.id,m);if(c.hasPhoto)l.add(c.id)}let o=t.query("SELECT ZOWNER AS owner, ZFULLNUMBER AS value FROM ZABCDPHONENUMBER").all(),d=t.query("SELECT ZOWNER AS owner, ZADDRESS AS value FROM ZABCDEMAILADDRESS").all();for(let c of[...o,...d]){let h=a.get(c.owner);if(!h||!c.value)continue;let m=x(c.value),b=l.has(c.owner)?{file:e,record:c.owner}:null,u=this.byHandle.get(m);if(!u)this.byHandle.set(m,{name:h,initials:tt(h),photo:b});else if(!u.photo&&b)u.photo=b}}finally{t.close()}}set(e,t){this.byHandle.set(x(e),{name:t,initials:tt(t)})}lookup(e){return this.byHandle.get(x(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 Xe(t.file,{readonly:!0});try{let n=s.query("SELECT ZTHUMBNAILIMAGEDATA AS thumb, ZIMAGEDATA AS full FROM ZABCDRECORD WHERE Z_PK = ?").get(t.record);return et(t.file,n?.thumb??null)??et(t.file,n?.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 st(e){let t=new Map;for(let s of e){let n=Lt(s),r=t.get(n);if(!r){t.set(n,{key:n,kind:s.style===H?"group":"direct",chatGuids:[s.guid],displayName:s.displayName,groupId:s.groupId,groupPhotoGuid:s.style===H?s.groupPhotoGuid:null,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(r.chatGuids.push(s.guid),r.kind==="group"&&!r.groupPhotoGuid)r.groupPhotoGuid=s.groupPhotoGuid;if(r.messageCount+=s.messageCount,r.recoverableCount+=s.recoverableCount,r.unreadCount+=s.unreadCount,!r.services.includes(s.service))r.services.push(s.service);for(let i of s.participants)if(r.kind==="group"&&!r.participants.includes(i))r.participants.push(i);if((s.lastMessageAt??0)>(r.lastMessageAt??0))r.lastMessageAt=s.lastMessageAt,r.lastMessageRowId=s.lastMessageRowId,r.lastMessageText=s.lastMessageText,r.lastMessageFromMe=s.lastMessageFromMe,r.displayName=s.displayName??r.displayName}return[...t.values()].sort((s,n)=>(n.lastMessageAt??0)-(s.lastMessageAt??0))}function ke(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!Je(t))return null;return`sms:${t}`}var Zt=`on run argv
40
+ set chatId to item 1 of argv
41
+ set theText to item 2 of argv
42
+ tell application "Messages"
43
+ set targetChat to chat id chatId
44
+ if theText is not "" then send theText to targetChat
45
+ repeat with i from 3 to (count of argv)
46
+ send (POSIX file (item i of argv)) to targetChat
47
+ end repeat
48
+ end tell
49
+ end run`;async function Ht(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...t],{stdout:"pipe",stderr:"pipe"}),[n,r,i]=await Promise.all([new Response(s.stdout).text(),new Response(s.stderr).text(),s.exited]);return{ok:i===0,output:(i===0?n:r).trim()}}var Gt={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function Jt(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}class z{provider="imessage";label="iMessage";databasePath;addressBookDir;openUrl;controller;confirmDeletes;platform;runScript;contacts=null;contactsLoadedAt=0;constructor(e={}){this.databasePath=e.databasePath??_e,this.addressBookDir=e.addressBookDir??Me,this.openUrl=e.openUrl??Jt,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??Bt.platform,this.runScript=e.runScript??Ht}capabilities(){return{send:!0,attachments:!0,replies:!1,reactions:!1,markRead:!1,live:!1}}async send(e,t){let s=t.text?.trim()??"",n=t.files??[];if(!s&&n.length===0)throw Error("Nothing to send.");let r=this.open((a)=>{let l=this.find(a,e);if(!l)return null;let[o]=a.messages(l.chatGuids,{newestFirst:!0,limit:1});return o?.chatGuid??l.chatGuids[0]??null});if(!r)throw Error("Messages has no chat for this conversation.");let i=await this.runScript(Zt,[r,s,...n.map((a)=>a.path)]);if(!i.ok)throw Error(`Messages did not send it: ${i.output||"no reason given"}`);return{id:null,sentAt:Date.now()}}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]),le(e.participants[0]),e.participants[0]);return[...new Set(t.filter((s)=>!!s))]}open(e){let t=new xe(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new J;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=J.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){if(e.startsWith("group:")){let t=this.open((s)=>s.attachmentFile(e.slice(6)));return W(t&&de(t)?new Uint8Array($t(t)):null)}return W(this.names().photo(e))}grouped=null;chats(e){let t=[this.databasePath,`${this.databasePath}-wal`].map((s)=>{try{let n=Wt(s);return`${n.mtimeMs}:${n.size}`}catch{return"-"}}).join("|");if(this.grouped?.stamp!==t)this.grouped={stamp:t,chats:st(e.chats())};return this.grouped.chats}find(e,t){return this.chats(e).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 ue&&e.needsFullDiskAccess&&de(jt(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)=>this.chats(e).map((t)=>this.toConversation(t)))}toConversation(e){let t=ke(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,avatar:e.kind==="group"&&e.groupPhotoGuid?`group:${e.groupPhotoGuid}`:null}}async messages(e,t={}){return this.open((s)=>{let n=this.find(s,e);if(!n)return[];let r=t.includeDeleted;return(t.after||!t.limit?s.messages(n.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:r}):s.messages(n.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:r}).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"?Gt[s?t.reactionType-1000:t.reactionType]??"\u2022":null,reactionRemoved:s,replyToId:t.replyToGuid,service:t.service,editedAt:t.editedAt,unsent:t.unsent,attachments:t.attachments.map((n)=>({id:n.guid,name:n.name,mimeType:n.mimeType,bytes:n.bytes,path:n.path&&de(n.path)?n.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((r)=>this.find(r,e)),s=t?ke(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 n=this.rowNames(t);if(this.controller&&n.length>0){let r=await this.controller.remove(n,s,this.confirmDeletes);return{mode:"confirm",removed:!1,detail:r.ok?this.confirmDeletes?"Deleted in Messages.":"Messages is asking you to confirm the delete.":r.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((n)=>this.find(n,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||!de(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 zt="im,mpim,public_channel,private_channel",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"},K=(e)=>Math.round(Number(e)*1000);function Kt(e){let t=Object.entries(nt).find(([,n])=>n===e);if(t)return t[0];let s=e.match(/^:([\w+-]+):$/);if(s)return s[1];throw Error(`Slack has no name for the reaction ${e}.`)}class V extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class q{provider="slack";label="Slack";token;fetch;apiBase;concurrency;appToken;Socket;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,this.appToken=e.appToken,this.Socket=e.WebSocket??globalThis.WebSocket}capabilities(){return{send:!0,attachments:!0,replies:!0,reactions:!0,markRead:!0,live:!!this.appToken}}async send(e,t){let s=t.text?.trim()??"",n=t.files??[];if(!s&&n.length===0)throw Error("Nothing to send.");let r=t.replyToId?await this.threadOf(e,t.replyToId):null;if(n.length===0){let a=await this.api("chat.postMessage",{channel:e,text:s,...r?{thread_ts:r}:{}},!0);return{id:a.ts,sentAt:K(a.ts)}}let i=[];for(let a of n){let l=await Bun.file(a.path).arrayBuffer(),o=a.name??a.path.split("/").pop()??"file",d=await this.api("files.getUploadURLExternal",{filename:o,length:l.byteLength},!0),c=await this.fetch(d.upload_url,{method:"POST",body:new Blob([l],{type:a.mimeType??"application/octet-stream"})});if(!c.ok)throw Error(`Slack did not take the upload of ${o}: ${c.status}`);i.push({id:d.file_id,title:o})}return await this.api("files.completeUploadExternal",{files:JSON.stringify(i),channel_id:e,...s?{initial_comment:s}:{},...r?{thread_ts:r}:{}},!0),{id:null,sentAt:Date.now()}}async threadOf(e,t){try{return(await this.api("conversations.history",{channel:e,latest:t,inclusive:!0,limit:1})).messages[0]?.thread_ts??t}catch{return t}}async react(e,t,s,n=!1){let r=Kt(s),i=t.split(":")[0];await this.api(n?"reactions.remove":"reactions.add",{channel:e,timestamp:i,name:r},!0)}async markRead(e){let s=(await this.api("conversations.history",{channel:e,limit:1})).messages[0];if(s)await this.api("conversations.mark",{channel:e,ts:s.ts},!0)}watch(e){let t=this.Socket;if(!this.appToken||!t)return()=>{};let s=!1,n=null,r=0,i=async()=>{if(s)return;try{let a=await this.fetch(`${this.apiBase}/apps.connections.open`,{method:"POST",headers:{authorization:`Bearer ${this.appToken}`}}),l=await a.json();if(!l.ok||!l.url)throw new V("apps.connections.open",l.error??`http_${a.status}`);let o=new t(l.url);n=o;let d=Promise.resolve();o.onmessage=(c)=>{r=0;let h;try{h=JSON.parse(String(c.data))}catch{return}if(h.envelope_id)o.send(JSON.stringify({envelope_id:h.envelope_id}));if(h.type==="disconnect"){o.close();return}d=d.then(()=>this.onEnvelope(h,e)).catch(()=>{})},o.onclose=()=>{if(n=null,!s)setTimeout(i,Math.min(60000,1000*2**Math.min(r++,6)))}}catch{if(!s)setTimeout(i,Math.min(60000,1000*2**Math.min(r++,6)))}};return i(),()=>{s=!0,n?.close()}}async onEnvelope(e,t){let s=e.payload?.event;if(e.type!=="events_api"||!s)return;if(s.type==="message"&&s.channel&&!s.subtype&&s.ts){let r=await this.toMessage(s.channel,s);t({type:"message",conversationId:s.channel,message:r});return}let n=s.channel??s.item?.channel??null;t({type:"changed",conversationId:n})}async api(e,t={},s=!1){for(let n=0;;n++){let r=new URLSearchParams(Object.entries(t).map(([l,o])=>[l,String(o)])),i=s?await this.fetch(`${this.apiBase}/${e}`,{method:"POST",headers:{authorization:`Bearer ${this.token}`,"content-type":"application/x-www-form-urlencoded"},body:r.toString()}):await this.fetch(`${this.apiBase}/${e}?${r}`,{headers:{authorization:`Bearer ${this.token}`}});if(i.status===429&&n<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 V(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(!oe(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:zt,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 n=0;n<t.length;n+=this.concurrency){let r=t.slice(n,n+this.concurrency);s.push(...await Promise.all(r.map((i)=>this.describe(i,e))))}return s.sort((n,r)=>(r.lastMessageAt??0)-(n.lastMessageAt??0))}async describe(e,t){let[{channel:s},n]=await Promise.all([this.api("conversations.info",{channel:e.id}).catch(()=>({channel:e})),this.api("conversations.history",{channel:e.id,limit:1}).catch(()=>({messages:[]}))]),r={...e,...s};this.channels.set(r.id,r);let i=n.messages[0],a=[];if(r.is_im)a=[await this.person(r.user)];else if(r.is_mpim){let l=await this.api("conversations.members",{channel:r.id,limit:50}).catch(()=>({members:[]}));a=await Promise.all(l.members.filter((o)=>o!==t.userId).map(async(o)=>await this.person(o)))}return{provider:"slack",id:r.id,kind:r.is_im?"direct":r.is_mpim?"group":"channel",title:r.is_im||r.is_mpim?null:`#${r.name}`,participants:a,workspace:t.team,lastMessageAt:i?K(i.ts):null,preview:i?.text||(i?.files?.length?"Attachment":null),previewFromMe:i?.user===t.userId,unread:r.unread_count_display??0,cursor:i?.ts??"0",visible:r.is_im||r.is_mpim?r.is_open!==!1:!0,archive:this.archiveSupport(r),url:`slack://channel?team=${t.teamId}&id=${r.id}`}}async messages(e,t={}){let s=await this.whoami(),n=[],r="",i=t.limit??1000;do{let l=await this.api("conversations.history",{channel:e,limit:Math.min(200,i),...t.after?{oldest:t.after,inclusive:!1}:{},...r?{cursor:r}:{}});n.push(...l.messages),r=l.has_more?l.response_metadata?.next_cursor??"":""}while(r&&n.length<i);n.sort((l,o)=>Number(l.ts)-Number(o.ts));let a=[];for(let l of n.slice(-i)){a.push(await this.toMessage(e,l));for(let o of l.reactions??[])for(let d of o.users??[])a.push({id:`${l.ts}:${o.name}:${d}`,conversationId:e,kind:"reaction",text:null,fromMe:d===s.userId,sentAt:K(l.ts),sender:d===s.userId?null:await this.person(d),targetId:l.ts,reaction:nt[o.name]??`:${o.name}:`,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:l.ts})}return a}async toMessage(e,t){let s=await this.whoami(),n=await this.person(t.user),r=!!t.subtype&&!["file_share","thread_broadcast","me_message"].includes(t.subtype);return{id:t.ts,conversationId:e,kind:r?"event":"message",text:t.text??null,fromMe:t.user===s.userId,sentAt:K(t.ts),sender:t.user===s.userId?null:n??(t.username?{id:t.bot_id??t.username,name:t.username}:null),targetId:null,reaction:null,reactionRemoved:!1,replyToId:t.thread_ts&&t.thread_ts!==t.ts?t.thread_ts:null,editedAt:t.edited?K(t.edited.ts):null,unsent:!1,attachments:(t.files??[]).map((i)=>({id:i.id,name:i.name??i.title??i.id,mimeType:i.mimetype??null,bytes:i.size??0,path:null,url:i.url_private??null})),cursor:t.ts}}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 he,readFileSync as ts}from"fs";import{basename as ss,dirname as Pe,extname as ns,join as Oe}from"path";import rs from"process";function Ee(e,t){let s=0n,n=0n;for(let r=t;r<e.length&&r<t+10;r++){let i=e[r];if(s|=BigInt(i&127)<<n,(i&128)===0)return{value:s,next:r+1};n+=7n}throw Error("Truncated varint")}function Vt(e){let t=[],s=0;while(s<e.length){let n=Ee(e,s);s=n.next;let r=Number(n.value>>3n),i=Number(n.value&7n);if(r===0)throw Error("Field number 0");if(i===0){let a=Ee(e,s);t.push({field:r,wire:i,value:a.value}),s=a.next}else if(i===1||i===5){let a=i===1?8:4;if(s+a>e.length)throw Error("Truncated fixed field");let l=0n;for(let o=a-1;o>=0;o--)l=l<<8n|BigInt(e[s+o]);t.push({field:r,wire:i,value:l}),s+=a}else if(i===2){let a=Ee(e,s),l=a.next,o=l+Number(a.value);if(o>e.length)throw Error("Truncated length-delimited field");t.push({field:r,wire:i,value:e.subarray(l,o)}),s=o}else throw Error(`Unsupported wire type ${i}`)}return t}function Y(e){if(!e||e.length===0)return null;try{return Vt(e)}catch{return null}}function qt(e,t){let s=e?.find((n)=>n.field===t&&n.wire===2);return s?s.value:null}function Ce(e,t){return(e??[]).filter((s)=>s.field===t&&s.wire===2).map((s)=>s.value)}function X(e,t){let s=qt(e,t);if(!s)return null;let n=new TextDecoder("utf-8",{fatal:!0});try{return n.decode(s)}catch{return null}}function Te(e,t){let s=e?.find((n)=>n.field===t&&n.wire!==2);return s?s.value:null}import{Database as Yt}from"bun:sqlite";import{homedir as Xt}from"os";import{join as Re}from"path";var rt=Re(Xt(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),Ie=Re(rt,"ChatStorage.sqlite"),it=Re(rt,"Message"),Q=0,Qt=1,es=4,p={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 pe 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 f(e){let t=e?.replace(/\uFFFC/g,"").trim();return t?t:null}class De{path;db;constructor(e=Ie){this.path=e;try{this.db=new Yt(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new pe(e,t)}}close(){this.db.close()}columns(e){try{return new Set(this.db.query(`PRAGMA table_info(${e})`).all().map((t)=>t.name))}catch{return new Set}}get hasMetadata(){return this.columns("ZWAMEDIAITEM").has("ZMETADATA")}get hasReceipts(){return this.columns("ZWAMESSAGE").has("ZMESSAGEINFO")&&this.columns("ZWAMESSAGEINFO").has("ZRECEIPTINFO")}sessions(e){return this.db.query(`
40
50
  SELECT s.Z_PK AS pk, s.ZCONTACTJID AS jid, s.ZSESSIONTYPE AS type, s.ZPARTNERNAME AS name,
41
51
  s.ZARCHIVED AS archived, s.ZUNREADCOUNT AS unread, s.ZLASTMESSAGEDATE AS lastDate,
42
52
  s.ZLASTMESSAGETEXT AS lastText, COALESCE(last.ZISFROMME, 0) AS lastFromMe,
@@ -44,17 +54,17 @@ var Ke=Object.defineProperty;var qe=(e)=>e;function Xe(e,t){this[e]=qe.bind(null
44
54
  (SELECT COUNT(*) FROM ZWAMESSAGE m WHERE m.ZCHATSESSION = s.Z_PK) AS messageCount
45
55
  FROM ZWACHATSESSION s
46
56
  LEFT JOIN ZWAMESSAGE last ON last.Z_PK = s.ZLASTMESSAGE
47
- WHERE s.ZSESSIONTYPE IN (${G}, ${Nt}, ${Ft})
57
+ WHERE s.ZSESSIONTYPE IN (${Q}, ${Qt}, ${es})
48
58
  AND COALESCE(s.ZHIDDEN, 0) = 0 AND COALESCE(s.ZREMOVED, 0) = 0
49
59
  AND s.ZCONTACTJID IS NOT NULL ${e?"AND s.ZCONTACTJID = ?":""}
50
60
  ORDER BY s.ZLASTMESSAGEDATE DESC
51
- `).all(...e?[e]:[]).map((r)=>({pk:r.pk,jid:r.jid,type:r.type,name:h(r.name),archived:!!r.archived,unread:r.unread==null?0:r.unread<0?1:r.unread,lastMessageAt:k(r.lastDate),lastMessageText:h(r.lastText),lastMessageFromMe:!!r.lastFromMe,lastMessagePk:r.lastPk,messageCount:r.messageCount}))}session(e){return this.sessions(e)[0]}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(`
61
+ `).all(...e?[e]:[]).map((s)=>({pk:s.pk,jid:s.jid,type:s.type,name:f(s.name),archived:!!s.archived,unread:s.unread==null?0:s.unread<0?1:s.unread,lastMessageAt:I(s.lastDate),lastMessageText:f(s.lastText),lastMessageFromMe:!!s.lastFromMe,lastMessagePk:s.lastPk,messageCount:s.messageCount}))}session(e){return this.sessions(e)[0]}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(`
52
62
  SELECT g.ZMEMBERJID AS jid, COALESCE(g.ZCONTACTNAME, g.ZFIRSTNAME, p.ZPUSHNAME) AS name
53
63
  FROM ZWAGROUPMEMBER g
54
64
  LEFT JOIN ZWAPROFILEPUSHNAME p ON p.ZJID = g.ZMEMBERJID
55
65
  WHERE g.ZCHATSESSION = ? AND COALESCE(g.ZISACTIVE, 1) = 1 AND g.ZMEMBERJID IS NOT NULL
56
66
  ORDER BY name
57
- `).all(e).map((r)=>({jid:r.jid,name:h(r.name)}))}messages(e,t={}){let r=t.newestFirst?"DESC":"ASC",s=t.limit?`LIMIT ${Math.max(1,Math.floor(t.limit))}`:"";return this.db.query(`
67
+ `).all(e).map((s)=>({jid:s.jid,name:f(s.name)}))}messages(e,t={}){let s=t.newestFirst?"DESC":"ASC",n=t.limit?`LIMIT ${Math.max(1,Math.floor(t.limit))}`:"";return this.db.query(`
58
68
  SELECT m.Z_PK AS pk, m.ZSTANZAID AS stanzaId, m.ZMESSAGETYPE AS type, m.ZTEXT AS text,
59
69
  m.ZISFROMME AS isFromMe, m.ZMESSAGEDATE AS date,
60
70
  g.ZMEMBERJID AS senderJid, COALESCE(g.ZCONTACTNAME, g.ZFIRSTNAME, p.ZPUSHNAME, m.ZPUSHNAME) AS senderName,
@@ -67,6 +77,6 @@ var Ke=Object.defineProperty;var qe=(e)=>e;function Xe(e,t){this[e]=qe.bind(null
67
77
  LEFT JOIN ZWAMEDIAITEM i ON i.Z_PK = m.ZMEDIAITEM
68
78
  ${this.hasReceipts?"LEFT JOIN ZWAMESSAGEINFO info ON info.Z_PK = m.ZMESSAGEINFO":""}
69
79
  WHERE m.ZCHATSESSION = ? AND m.Z_PK > ?
70
- ORDER BY m.Z_PK ${r}
71
- ${s}
72
- `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:k(i.date)??0,senderJid:i.senderJid,senderName:h(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var Wt="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",Bt={[d.Image]:"image/jpeg",[d.Video]:"video/mp4",[d.Audio]:"audio/ogg",[d.Gif]:"video/mp4",[d.Sticker]:"image/webp"},Zt={[d.Image]:"Photo",[d.Video]:"Video",[d.Audio]:"Voice message",[d.Document]:"Document",[d.Gif]:"GIF",[d.Sticker]:"Sticker"},Ht={".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 Gt(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function ke(e){let[t=e,r]=e.split("@");return r==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function Jt(e){let t=e?.match(/^FN:(.*)$/m);return h(t?.[1])}class J{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??Se,this.mediaRoot=e.mediaRoot??(e.databasePath?Ee(we(e.databasePath),"Message"):Je),this.controller=e.controller,this.openUrl=e.openUrl??Gt,this.platform=e.platform??jt.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=Ee(we(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((r)=>oe(r))??null}pictures(e){let t=e.profilePicturePaths(),r=new Map;return(s)=>{if(!r.has(s))r.set(s,this.pictureFile(t.get(s))?s:null);return r.get(s)}}async avatar(e){let t=this.open((r)=>this.pictureFile(r.profilePicturePaths().get(e)));return N(t?new Uint8Array(Lt(t)):null)}open(e){let t=new Ce(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 ae&&e.needsFullDiskAccess&&oe(we(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:Wt};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,r]=e.jid.split("@");return r==="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((r)=>{let s=r.type===G,n=s?[{id:r.jid,name:r.name,avatar:t(r.jid)}]:e.members(r.pk).map((i)=>({id:i.jid,name:i.name,avatar:t(i.jid)}));return{provider:"whatsapp",id:r.jid,kind:s?"direct":"group",title:s?null:r.name,participants:n,workspace:null,lastMessageAt:r.lastMessageAt,preview:r.lastMessageText,previewFromMe:r.lastMessageFromMe,unread:r.unread,cursor:String(r.lastMessagePk),visible:!r.archived,archive:this.archiveSupport(r),url:this.url(r),avatar:s?null:t(r.jid)}})})}async messages(e,t={}){return this.open((r)=>{let s=r.session(e);if(!s)return[];let n=t.after||!t.limit?r.messages(s.pk,{afterPk:Number(t.after)||0,limit:t.limit}):r.messages(s.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(r),a=new Map(s.type===G?[]:r.members(s.pk).map((o)=>[o.jid,o.name]));return n.flatMap((o)=>[this.toMessage(s,o,i),...this.reactions(s,o,a,i)])})}toMessage(e,t,r){let s=t.isFromMe?null:e.type===G?{id:e.jid,name:e.name,avatar:r(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:r(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:s,targetId:null,reaction:null,reactionRemoved:!1,replyToId:H(Z(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,r,s){let n=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Ae(Z(t.receipts),7))Ae(Z(a),1).forEach((o,l)=>{let g=Z(o),c=H(g,3);if(!c)return;let m=H(g,2),f=!m&&xe(g,6)===1n,v=m??(f?null:e.jid),u=Number(xe(g,4)??0n);i.push({id:H(g,1)??`${n}:reaction:${l}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:f,sentAt:u>0?u:t.sentAt,sender:v?{id:v,name:r.get(v)??(v===e.jid?e.name:null),avatar:s(v)}:null,targetId:n,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case d.Text:case d.Link:return{kind:"message",text:h(e.text),unsent:!1};case d.Image:case d.Video:case d.Gif:return{kind:"message",text:h(e.text)??h(t?.title),unsent:!1};case d.Contact:{let r=Jt(t?.vcard??null)??h(t?.title);return{kind:"message",text:r?`Contact card: ${r}`:"Contact card",unsent:!1}}case d.Location:{let r=h(t?.title),s=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:[r??"Shared a location",s].filter(Boolean).join(" "),unsent:!1}}case d.GroupEvent:case d.Call:return{kind:"event",text:h(e.text),unsent:!1};case d.Deleted:return{kind:"message",text:null,unsent:!0};case d.Audio:case d.Document:case d.Sticker:return{kind:"message",text:null,unsent:!1};default:return h(e.text)||t?.path?{kind:"message",text:h(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,r=Zt[e.type];if(!t||!t.path&&!r||e.type===d.Text||e.type===d.Link)return[];let s=t.path?Ee(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?$t(t.path):null,a=e.type===d.Document?h(t.title)??h(e.text)??i??r:i??r;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?Ht[Ut(t.path).toLowerCase()]:void 0)??Bt[e.type]??null,bytes:t.bytes,path:s&&oe(s)?s:null,url:null}]}rowNames(e){return[...new Set([e.name,ke(e.jid)].filter((t)=>!!t))]}async settled(e,t){let r=Date.now()+this.settleMs;while(!0){if(this.open((s)=>s.session(e))?.archived===t)return!0;if(Date.now()>=r)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((s)=>s.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 s=await this.controller.archive(this.rowNames(t));if(!s.ok)return{mode:"native",removed:!1,detail:s.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 r=this.url(t);if(r)this.openUrl(r);return{mode:"confirm",removed:!1,detail:r?"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((s)=>s.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||!oe(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 zt(e,t){switch(e){case"imessage":return new W(t);case"slack":return new B(t);case"discord":return new F(t);case"whatsapp":return new J(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}async function as(e,t={}){let r=t.driver??"slack";switch(r){case"slack":return K(e);case"discord":return V(e);case"teams":return X(e);default:return{success:!1,provider:r,message:`Unknown chat driver: ${r}`}}}async function os(e,t,r){return q(e,t,r)}async function ls(e,t,r){return R(e,t,r)}async function cs(e,t){return Y(e,t)}function us(e){I(e)}function ds(e){_(e)}function ms(e){P(e)}export{ds as configureDiscord,us as configureSlack,ms as configureTeams,le as discord,ze as inbox,as as send,ls as sendToDiscord,os as sendToSlack,cs as sendToTeams,ce as slack,de as teams};
80
+ ORDER BY m.Z_PK ${s}
81
+ ${n}
82
+ `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:I(i.date)??0,senderJid:i.senderJid,senderName:f(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var is="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",as={[p.Image]:"image/jpeg",[p.Video]:"video/mp4",[p.Audio]:"audio/ogg",[p.Gif]:"video/mp4",[p.Sticker]:"image/webp"},os={[p.Image]:"Photo",[p.Video]:"Video",[p.Audio]:"Voice message",[p.Document]:"Document",[p.Gif]:"GIF",[p.Sticker]:"Sticker"},ls={".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 cs(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function Ne(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function us(e){let t=e?.match(/^FN:(.*)$/m);return f(t?.[1])}class ee{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??Ie,this.mediaRoot=e.mediaRoot??(e.databasePath?Oe(Pe(e.databasePath),"Message"):it),this.controller=e.controller,this.openUrl=e.openUrl??cs,this.platform=e.platform??rs.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=Oe(Pe(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>he(s))??null}pictures(e){let t=e.profilePicturePaths(),s=new Map;return(n)=>{if(!s.has(n))s.set(n,this.pictureFile(t.get(n))?n:null);return s.get(n)}}async avatar(e){let t=this.open((s)=>this.pictureFile(s.profilePicturePaths().get(e)));return W(t?new Uint8Array(ts(t)):null)}open(e){let t=new De(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 pe&&e.needsFullDiskAccess&&he(Pe(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:is};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 n=s.type===Q,r=n?[{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:n?"direct":"group",title:n?null:s.name,participants:r,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:n?null:t(s.jid)}})})}async messages(e,t={}){return this.open((s)=>{let n=s.session(e);if(!n)return[];let r=t.after||!t.limit?s.messages(n.pk,{afterPk:Number(t.after)||0,limit:t.limit}):s.messages(n.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(s),a=new Map(n.type===Q?[]:s.members(n.pk).map((l)=>[l.jid,l.name]));return r.flatMap((l)=>[this.toMessage(n,l,i),...this.reactions(n,l,a,i)])})}toMessage(e,t,s){let n=t.isFromMe?null:e.type===Q?{id:e.jid,name:e.name,avatar:s(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:s(t.senderJid)}:null,{kind:r,text:i,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:r,text:i,fromMe:t.isFromMe,sentAt:t.sentAt,sender:n,targetId:null,reaction:null,reactionRemoved:!1,replyToId:X(Y(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,s,n){let r=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Ce(Y(t.receipts),7))Ce(Y(a),1).forEach((l,o)=>{let d=Y(l),c=X(d,3);if(!c)return;let h=X(d,2),m=!h&&Te(d,6)===1n,b=h??(m?null:e.jid),u=Number(Te(d,4)??0n);i.push({id:X(d,1)??`${r}:reaction:${o}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:m,sentAt:u>0?u:t.sentAt,sender:b?{id:b,name:s.get(b)??(b===e.jid?e.name:null),avatar:n(b)}:null,targetId:r,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case p.Text:case p.Link:return{kind:"message",text:f(e.text),unsent:!1};case p.Image:case p.Video:case p.Gif:return{kind:"message",text:f(e.text)??f(t?.title),unsent:!1};case p.Contact:{let s=us(t?.vcard??null)??f(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case p.Location:{let s=f(t?.title),n=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",n].filter(Boolean).join(" "),unsent:!1}}case p.GroupEvent:case p.Call:return{kind:"event",text:f(e.text),unsent:!1};case p.Deleted:return{kind:"message",text:null,unsent:!0};case p.Audio:case p.Document:case p.Sticker:return{kind:"message",text:null,unsent:!1};default:return f(e.text)||t?.path?{kind:"message",text:f(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=os[e.type];if(!t||!t.path&&!s||e.type===p.Text||e.type===p.Link)return[];let n=t.path?Oe(this.mediaRoot,t.path):null,r=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?ss(t.path):null,a=e.type===p.Document?f(t.title)??f(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:r??(t.path?ls[ns(t.path).toLowerCase()]:void 0)??as[e.type]??null,bytes:t.bytes,path:n&&he(n)?n:null,url:null}]}rowNames(e){return[...new Set([e.name,Ne(e.jid)].filter((t)=>!!t))]}async settled(e,t){let s=Date.now()+this.settleMs;while(!0){if(this.open((n)=>n.session(e))?.archived===t)return!0;if(Date.now()>=s)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((n)=>n.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 n=await this.controller.archive(this.rowNames(t));if(!n.ok)return{mode:"native",removed:!1,detail:n.detail};let r=await this.settled(e,!0);return{mode:"native",removed:r,detail:r?"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((n)=>n.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||!he(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 ds(e,t){switch(e){case"imessage":return new z(t);case"slack":return new q(t);case"discord":return new Z(t);case"whatsapp":return new ee(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}async function Tn(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return ne(e);case"discord":return se(e);case"teams":return ie(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Rn(e,t,s){return re(e,t,s)}async function In(e,t,s){return O(e,t,s)}async function Dn(e,t){return ae(e,t)}function Pn(e){N(e)}function On(e){D(e)}function Nn(e){F(e)}export{On as configureDiscord,Pn as configureSlack,Nn as configureTeams,me as discord,at as inbox,Tn as send,In as sendToDiscord,Rn as sendToSlack,Dn as sendToTeams,ge as slack,be 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.63",
5
+ "version": "0.75.65",
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.63"
61
+ "@stacksjs/logging": "0.75.65"
62
62
  },
63
63
  "devDependencies": {
64
- "@stacksjs/cli": "0.75.63",
65
- "@stacksjs/config": "0.75.63",
66
- "@stacksjs/error-handling": "0.75.63",
67
- "@stacksjs/types": "0.75.63",
64
+ "@stacksjs/cli": "0.75.65",
65
+ "@stacksjs/config": "0.75.65",
66
+ "@stacksjs/error-handling": "0.75.65",
67
+ "@stacksjs/types": "0.75.65",
68
68
  "better-dx": "^0.2.24"
69
69
  }
70
70
  }