@stacksjs/chat 0.75.422 → 0.75.423

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,12 @@
1
1
  import { BaseChatDriver } from './base';
2
2
  import type { ChatMessage, ChatResult, RenderOptions } from '@stacksjs/types';
3
+ /**
4
+ * A message in Discord's own field names. `avatarUrl` and `allowedMentions`
5
+ * used to be sent as they are spelled here, which Discord ignores, so an
6
+ * explicit `allowedMentions: { parse: [] }` protected nothing and the avatar
7
+ * never showed.
8
+ */
9
+ export declare function discordPayload(message: DiscordMessage): Record<string, unknown>;
3
10
  export declare function configure(options: DiscordConfig): void;
4
11
  /**
5
12
  * The settings a send uses: what `configure()` set, then the app's
@@ -20,6 +27,13 @@ export declare function sendWebhook(webhookUrl: string, content: string, options
20
27
  * Send an embed message via Discord webhook
21
28
  */
22
29
  export declare function sendEmbed(webhookUrl: string, embed: DiscordEmbed, options?: Partial<DiscordMessage>): Promise<ChatResult>;
30
+ /**
31
+ * Mentions a message may ping unless it says otherwise: people and roles named
32
+ * by ID, never `@everyone` or `@here`. Without `allowed_mentions` Discord
33
+ * parses every mention in the content, so a notification carrying a comment
34
+ * that said `@everyone` pinged the whole server.
35
+ */
36
+ export declare const DEFAULT_ALLOWED_MENTIONS: NonNullable<DiscordMessage['allowedMentions']>;
23
37
  export declare const driver: DiscordDriver;
24
38
  declare const __dtsx_default_export__: {
25
39
  send: typeof send;
@@ -1,5 +1,14 @@
1
1
  import { BaseChatDriver } from './base';
2
2
  import type { ChatMessage, ChatResult, RenderOptions } from '@stacksjs/types';
3
+ /**
4
+ * `ChatMessage.content` is plain text, and Slack reads `&`, `<` and `>` as
5
+ * markup, so they are escaped as Slack's docs ask for literal text. Unescaped,
6
+ * a body carrying `<!channel>` pinged the whole channel and
7
+ * `<https://evil.test|your invoice>` showed as "your invoice". Plain URLs
8
+ * still turn into links. `sendWebhook(url, text)` stays raw, for markup
9
+ * written on purpose.
10
+ */
11
+ export declare function escapeSlackText(text: string): string;
3
12
  export declare function configure(options: SlackConfig): void;
4
13
  /**
5
14
  * The settings a send uses: what `configure()` set, then the app's
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var bt=Object.defineProperty;var vt=(e)=>e;function wt(e,t){this[e]=vt.bind(null,t)}var ae=(e,t)=>{for(var s in t)bt(e,s,{get:t[s],enumerable:!0,configurable:!0,set:wt.bind(t,s)})};var we={};ae(we,{DiscordDriver:()=>L,Driver:()=>L,configure:()=>F,default:()=>xt,driver:()=>At,resolveConfig:()=>Ge,send:()=>oe,sendEmbed:()=>Je,sendWebhook:()=>W});var Ae={};ae(Ae,{Driver:()=>B,SlackDriver:()=>B,configure:()=>j,default:()=>Mt,driver:()=>kt,resolveConfig:()=>Ve,send:()=>le,sendWebhook:()=>ce});var _e={};ae(_e,{Driver:()=>H,TeamsDriver:()=>H,assertWebhookUrl:()=>N,configure:()=>Z,default:()=>Tt,driver:()=>Et,resolveConfig:()=>Ke,send:()=>ue,sendCard:()=>qe,sendWebhook:()=>de});var ft={};ae(ft,{DiscordInboxDriver:()=>V,IMessageDriver:()=>X,SlackApiError:()=>ee,SlackInboxDriver:()=>te,WhatsAppDriver:()=>ie,createInboxDriver:()=>xs,decodeAttributedBody:()=>pe,formatHandle:()=>he,formatJid:()=>Ze,normalizeHandle:()=>k,snowflakeTime:()=>Ce});import{log as ve}from"@stacksjs/logging";async function P(){let{awaitConfig:e}=await import("@stacksjs/config");return(await e()).services??{}}function _(e){return(e==null?"":String(e).trim())||void 0}function M(e){let t=Number(e);return e!==""&&e!=null&&Number.isFinite(t)&&t>=0?t:void 0}import{log as St}from"@stacksjs/logging";class C{config;constructor(e){this.config={...e,maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000}}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));St.error(`[${this.name}] Message sending failed`,{error:s.message,stack:s.stack?.replace(/https?:\/\/[^\s)\]"']+/g,"[REDACTED-URL]"),to:t.to,subject:t.subject});let r={message:`Message sending failed: ${s.message}`,success:!1,provider:this.name};if(t.onError){let n=t.onError(s),i=n instanceof Promise?await n:n;r={...r,...i,success:!1,provider:this.name}}return r}async handleSuccess(e,t){let s={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let r=e.handle(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let r=e.onSuccess(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}}catch(r){return this.handleError(r,e)}return s}}var R={};function He(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/discord] webhookUrl is not a valid URL: ${e}`)}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.`)}function F(e){if(e.webhookUrl)He(e.webhookUrl);R={...R,...e}}async function Ge(){let e={};try{e=(await P()).discord??{}}catch(s){ve.debug(`[chat/discord] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=R.webhookUrl??_(e.webhookUrl);if(t&&!R.webhookUrl)He(t);return{webhookUrl:t,botToken:R.botToken??_(e.botToken),maxRetries:R.maxRetries??M(e.maxRetries),retryTimeout:R.retryTimeout??M(e.retryTimeout)}}class L extends C{name="discord";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)F(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};ve.info("Sending message via Discord...",s);try{return this.validateMessage(e),this.settings=await Ge(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.id)}catch(r){if(s<this.config.maxRetries)return ve.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(this.settings.webhookUrl)return this.sendViaWebhook(e);if(this.settings.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(this.settings.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){let n=await s.text();throw Error(`Discord webhook failed: ${s.status} - ${n}`)}if(s.status===204)return{};return{id:(await s.json()).id}}async sendViaBotToken(e){let t=e.to,s={content:e.content};if(e.subject||e.template)s.embeds=[this.buildEmbed(e)];let r=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${this.settings.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let i=await r.json();throw Error(`Discord API failed: ${r.status} - ${i.message}`)}return{id:(await r.json()).id}}buildEmbed(e){let t={};if(e.subject)t.title=e.subject;if(e.content)t.description=e.content;return t.color=5793266,t.timestamp=new Date().toISOString(),t}}async function oe(e,t){return new L().send(e,t)}async function W(e,t,s){try{let r={content:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"discord",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"discord",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"discord",message:(r instanceof Error?r:Error(String(r))).message}}}async function Je(e,t,s){return W(e,"",{...s,embeds:[t]})}var At=new L,xt={send:oe,sendWebhook:W,sendEmbed:Je,configure:F,DiscordDriver:L};import{log as Se}from"@stacksjs/logging";var I={};function ze(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/slack] webhookUrl is not a valid URL: ${e}`)}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.`)}function j(e){if(e.webhookUrl)ze(e.webhookUrl);I={...I,...e}}async function Ve(){let e={};try{e=(await P()).slack??{}}catch(s){Se.debug(`[chat/slack] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=I.webhookUrl??_(e.webhookUrl);if(t&&!I.webhookUrl)ze(t);return{webhookUrl:t,botToken:I.botToken??_(e.botToken),maxRetries:I.maxRetries??M(e.maxRetries),retryTimeout:I.retryTimeout??M(e.retryTimeout)}}class B extends C{name="slack";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)j(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Se.info("Sending message via Slack...",s);try{return this.validateMessage(e),this.settings=await Ve(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.ts)}catch(r){if(s<this.config.maxRetries)return Se.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(this.settings.webhookUrl)return this.sendViaWebhook(e);if(this.settings.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let s=e.content||e.subject||"",n={text:s.length>40000?`${s.slice(0,39986)}\u2026 [truncated]`:s,username:e.from?.name,mrkdwn:!0};if(e.template)n.blocks=this.buildBlocks(e);let i=await fetch(this.settings.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){let a=await i.text();throw Error(`Slack webhook failed: ${i.status} - ${a}`)}return{}}async sendViaBotToken(e){let t={channel:e.to,text:e.content||e.subject,mrkdwn:!0};if(e.template)t.blocks=this.buildBlocks(e);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${this.settings.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok)throw Error(`Slack API failed: ${s.status}`);let r=await s.json();if(!r.ok)throw Error(`Slack API error: ${r.error}`);return{ts:r.ts}}buildBlocks(e){let t=[];if(e.subject)t.push({type:"header",text:{type:"plain_text",text:e.subject,emoji:!0}});if(e.content)t.push({type:"section",text:{type:"mrkdwn",text:e.content}});return t}}async function le(e,t){return new B().send(e,t)}async function ce(e,t,s){try{let r={text:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"slack",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"slack",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"slack",message:(r instanceof Error?r:Error(String(r))).message}}}var kt=new B,Mt={send:le,sendWebhook:ce,configure:j,SlackDriver:B};import{log as xe}from"@stacksjs/logging";var O={},_t=/(?:^|\.)(?:api\.powerplatform\.com|api\.powerautomate\.com|flow\.microsoft\.com|logic\.azure\.(?:com|us|cn))$/i;function N(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/teams] webhookUrl is not a valid URL: ${e}`)}if(t.protocol!=="https:")throw Error("[chat/teams] webhookUrl must use https://");if(/(?:^|\.)webhook\.office\.com$/i.test(t.hostname))throw Error('[chat/teams] webhookUrl is an Office 365 connector webhook, which Microsoft retired in May 2026 and no longer delivers. Create one with the Workflows app in Teams ("Post to a channel when a webhook request is received") and use its URL.');if(!_t.test(t.hostname))throw Error(`[chat/teams] webhookUrl host "${t.hostname}" is not a Teams Workflows (Power Automate) host.`)}function Z(e){if(e.webhookUrl)N(e.webhookUrl);O={...O,...e}}async function Ke(){let e={};try{e=(await P()).teams??{}}catch(s){xe.debug(`[chat/teams] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=O.webhookUrl??_(e.webhookUrl);if(t&&!O.webhookUrl)N(t);return{webhookUrl:t,maxRetries:O.maxRetries??M(e.maxRetries),retryTimeout:O.retryTimeout??M(e.retryTimeout)}}class H extends C{name="teams";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)Z(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};xe.info("Sending message via Microsoft Teams...",s);try{return this.validateMessage(e),this.settings=await Ke(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(r){if(s<this.config.maxRetries)return xe.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){let r=this.settings.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!r)throw Error("Teams webhook URL not configured");N(r);let n=this.buildPayload(e),i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){let a=await i.text();throw Error(`Teams webhook failed: ${i.status} - ${a}`)}}buildPayload(e){return Me(this.buildAdaptiveCard(e),e.subject||e.content?.substring(0,50)||"Notification")}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});if(!e.subject&&!e.template)return ke(t);return t.push({type:"TextBlock",text:new Date().toLocaleString(),size:"Small",color:"Dark",wrap:!0}),ke(t)}}function ke(e){return{type:"AdaptiveCard",version:"1.4",$schema:"http://adaptivecards.io/schemas/adaptive-card.json",body:e}}function Me(e,t){return{type:"message",summary:t,attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:e}]}}async function ue(e,t){return new H().send(e,t)}async function de(e,t){try{N(e);let s=Me(ke([{type:"TextBlock",text:t,wrap:!0}]),t.substring(0,50)||"Notification"),r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let n=await r.text();return{success:!1,provider:"teams",message:`Webhook failed: ${r.status} - ${n}`}}return{success:!0,provider:"teams",message:"Message sent successfully"}}catch(s){return{success:!1,provider:"teams",message:(s instanceof Error?s:Error(String(s))).message}}}async function qe(e,t,s){try{N(e);let r=Me(t,s||"Notification"),n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"teams",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"teams",message:"Card sent successfully"}}catch(r){return{success:!1,provider:"teams",message:(r instanceof Error?r:Error(String(r))).message}}}var Et=new H,Tt={send:ue,sendWebhook:de,sendCard:qe,configure:Z,TeamsDriver:H};var x={DISPATCH:0,HEARTBEAT:1,IDENTIFY:2,RESUME:6,RECONNECT:7,INVALID_SESSION:9,HELLO:10,HEARTBEAT_ACK:11},Ct=new Set([4004,4010,4011,4012,4013,4014]);class Ee{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 r=setTimeout(()=>s(Error("Discord did not answer in time.")),e);this.readyResolvers.push((n)=>{clearTimeout(r),t(n)})})}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 x.HELLO:if(this.startHeartbeat(s.d?.heartbeat_interval??41250),this.sessionId)this.send({op:x.RESUME,d:{token:this.identity.token,session_id:this.sessionId,seq:this.sequence}});else this.identify();break;case x.HEARTBEAT:this.send({op:x.HEARTBEAT,d:this.sequence});break;case x.HEARTBEAT_ACK:this.acknowledged=!0;break;case x.RECONNECT:e.close();break;case x.INVALID_SESSION:if(!s.d)this.sessionId=null,this.sequence=null;setTimeout(()=>this.sessionId?e.close():this.identify(),1000+Math.random()*4000);break;case x.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 r of this.readyResolvers.splice(0))r(s.d)}if(s.t)this.options.onDispatch(s.t,s.d);break}}identify(){this.send({op:x.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:x.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(Ct.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 r=Math.min(60000,1000*2**Math.min(this.failures++,6));setTimeout(()=>this.connect(),r)}}var Rt=1n<<10n,It=1n<<16n,Dt=1n<<3n,E=(e)=>BigInt(e??0);function Ye(e){if(e.ownerId&&e.ownerId===e.userId)return~0n;let t=new Map(e.roles.map((o)=>[o.id,o])),s=E(t.get(e.guildId)?.permissions);for(let o of e.memberRoles)s|=E(t.get(o)?.permissions);if(s&Dt)return~0n;let r=s,n=e.overwrites.find((o)=>o.id===e.guildId);if(n)r=r&~E(n.deny)|E(n.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|=E(o.allow),a|=E(o.deny);r=r&~a|i;let l=e.overwrites.find((o)=>(o.type===1||o.type==="member")&&o.id===e.userId);if(l)r=r&~E(l.deny)|E(l.allow);return r}function Xe(e){return(e&Rt)!==0n&&(e&It)!==0n}function Pt(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 G(e){if(!e||e.length===0)return new Response("Not found",{status:404});return new Response(Uint8Array.from(e),{headers:{"content-type":Pt(e)}})}function me(e,t){try{let s=new URL(e);return s.protocol==="https:"&&t.some((r)=>s.hostname===r||s.hostname.endsWith(`.${r}`))}catch{return!1}}var Qe=new Set([0,5]),J=new Set([10,11,12]),b=1,S=3,Ot={os:"Mac OS X",browser:"Attic",device:"Attic"};function Ce(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}var et=(e,t)=>!!e&&(!t||BigInt(e)>BigInt(t)),Te=(e)=>e.avatar?`https://cdn.discordapp.com/avatars/${e.id}/${e.avatar}.png?size=128`:null,z=(e)=>e.global_name||e.username;class V{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;previews=new Map;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}}async api(e,t={}){for(let s=0;;s++){let r=typeof t.body==="string",n=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{...this.headers(),...r?{"content-type":"application/json"}:{},...t.headers??{}}});if(n.status===429&&s<3){let i=await n.json().catch(()=>({}));await Bun.sleep(Math.min(30,i.retry_after??1)*1000);continue}if(!n.ok){let i=await n.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${i.message??n.status}`)}return n.status===204?void 0:await n.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 ${z(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 Ee({token:this.token,properties:Ot,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 n of e.users??[])this.users.set(n.id,n);for(let n of e.private_channels??[]){for(let i of n.recipients??[])this.users.set(i.id,i);this.channels.set(n.id,n)}let t=Array.isArray(e.read_state)?e.read_state:e.read_state?.entries??[];for(let n of t)this.readStates.set(n.id,n);let s=Array.isArray(e.user_guild_settings)?e.user_guild_settings:e.user_guild_settings?.entries??[];for(let n of s){if(n.muted&&n.guild_id)this.muted.add(n.guild_id);for(let i of n.channel_overrides??[])if(i.muted)this.muted.add(i.channel_id)}(e.guilds??[]).forEach((n,i)=>{let a=n.properties?.name??n.name??"Server",l=n.properties?.icon??n.icon??null;this.guilds.set(n.id,a),this.icons.set(n.id,l?`https://cdn.discordapp.com/icons/${n.id}/${l}.png?size=128`:null);let o=e.merged_members?.[i]?.[0]?.roles??n.members?.find((c)=>c.user?.id===this.userId)?.roles??null,d=n.properties?.owner_id??n.owner_id??null;for(let c of[...n.channels??[],...n.threads??[]]){if(!Qe.has(c.type)&&!J.has(c.type))continue;let h={...c,guild_id:n.id};if(this.readStates.has(c.id)||o!==null&&Xe(Ye({guildId:n.id,ownerId:d,userId:this.userId??"",memberRoles:o,roles:n.roles??[],overwrites:(J.has(c.type)?(n.channels??[]).find((y)=>y.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);this.previews.set(t.channel_id,{id:t.id,text:t.content||(t.attachments?.length?"Attachment":null),fromMe:t.author?.id===this.userId,at:Date.parse(t.timestamp)||Date.now()});let r=(t.mentions??[]).some((i)=>i.id===this.userId),n=this.muted.has(t.channel_id)||!!t.guild_id&&this.muted.has(t.guild_id);if(this.listed.has(t.channel_id)||s?.type===b||s?.type===S||!s&&!t.guild_id||r)if(n&&!r)this.emit({type:"changed",conversationId:t.channel_id});else 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===b||t.type===S){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===b||t.type===S)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===b||this.channels.get(t.channel_id)?.type===S)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===b)return{mode:"native",detail:"Closes the DM in Discord. It comes back when they write."};if(e.type===S)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 r of e){this.guilds.set(r.id,r.name),this.icons.set(r.id,r.icon?`https://cdn.discordapp.com/icons/${r.id}/${r.icon}.png?size=128`:null);let[n,i]=await Promise.all([this.api(`/guilds/${r.id}/channels`).catch(()=>[]),this.api(`/guilds/${r.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...n.filter((a)=>Qe.has(a.type)).map((a)=>({...a,guild_id:r.id}))),t.push(...i.threads.map((a)=>({...a,guild_id:a.guild_id??r.id})))}let s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,!0))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async userConversations(){if(!this.session?.ready)this.absorbReady(await this.gateway().whenReady());let e=[...this.channels.values()].filter((r)=>r.type===b||r.type===S||this.readStates.has(r.id)).sort((r,n)=>et(r.last_message_id,n.last_message_id)?-1:1),t=new Set(e.filter((r)=>r.type===b||r.type===S).slice(0,40).map((r)=>r.id)),s=[];for(let r=0;r<e.length;r+=this.concurrency){let n=e.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,t.has(i.id)))))}return this.listed=new Set(s.map((r)=>r.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=this.previews.get(e.id);if(t&&e.last_message_id&&s?.id!==e.last_message_id){let p=(await this.api(`/channels/${e.id}/messages?limit=1`).catch(()=>[]))[0];if(p)s={id:p.id,text:p.content||(p.attachments?.length?"Attachment":null),fromMe:p.author.id===this.userId,at:Date.parse(p.timestamp)},this.previews.set(e.id,s)}if(s&&e.last_message_id&&s.id!==e.last_message_id)s=void 0;let r=J.has(e.type),n=e.type===b||e.type===S,i=n?this.recipients(e):[],a=e.type===b?i[0]?z(i[0]):"Direct message":e.type===S?e.name||i.map(z).join(", ")||"Group":r?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&&et(e.last_message_id,l?.last_message_id??(n?null:e.last_message_id)),c=!this.isUser?0:l?.mention_count||(d&&(n||!o)?1:0),h=e.type===b?i[0]?Te(i[0]):null:e.type===S?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===b?"direct":e.type===S?"group":r?"thread":"channel",title:a,participants:i.map((p)=>({id:p.id,name:z(p),avatar:Te(p)})),workspace:e.guild_id?this.guilds.get(e.guild_id)??null:null,avatar:h,lastMessageAt:s?s.at:Ce(e.last_message_id),preview:s?.text??null,previewFromMe:!!s?.fromMe,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,r=[],n=(i,a)=>BigInt(i.id)<BigInt(a.id)?-1:1;if(t.after){let i=t.after;while(r.length<s){let a=await this.api(`/channels/${e}/messages?limit=100&after=${i}`);if(a.length===0)break;if(a.sort(n),r.push(...a),i=a[a.length-1].id,a.length<100)break}}else{let i="";while(r.length<s){let a=await this.api(`/channels/${e}/messages?limit=${Math.min(100,s-r.length)}${i?`&before=${i}`:""}`);if(a.length===0)break;if(a.sort(n),r.unshift(...a),i=a[0].id,a.length<100)break}}return r.slice(0,s).flatMap((i)=>this.toMessage(e,i))}toMessage(e,t){let s=t.author.id===this.userId,r=[{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:z(t.author),avatar:Te(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((n)=>({id:n.id,name:n.filename,mimeType:n.content_type??null,bytes:n.size,path:null,url:n.url})),cursor:t.id}];for(let n of t.reactions??[])r.push({id:`${t.id}:${n.emoji.id??n.emoji.name}`,conversationId:e,kind:"reaction",text:n.count>1?String(n.count):null,fromMe:!!n.me,sentAt:Date.parse(t.timestamp),sender:null,targetId:t.id,reaction:n.emoji.name??"\u2022",reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:t.id});return r}async send(e,t){let s=t.text?.trim()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=String(BigInt(Date.now())-1420070400000n<<22n),i={content:s,nonce:n,tts:!1};if(t.replyToId)i.message_reference={message_id:t.replyToId,channel_id:e};let a;if(r.length===0)a=await this.api(`/channels/${e}/messages`,{method:"POST",body:JSON.stringify(i)});else{let o=new FormData;i.attachments=r.map((d,c)=>({id:c,filename:d.name??d.path.split("/").pop()})),o.append("payload_json",JSON.stringify(i));for(let[d,c]of r.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,r=!1){let n=t.split(":")[0];await this.api(`/channels/${e}/messages/${n}/reactions/${encodeURIComponent(s)}/@me`,{method:r?"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===b)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===b){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(!me(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 fe,readFileSync as Yt,statSync as Xt}from"fs";import{dirname as Qt}from"path";import es from"process";import{Database as Lt}from"bun:sqlite";import{homedir as it}from"os";import{join as at}from"path";function tt(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,r=e[s+6],n=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 T=0;for(let g=0;g<v;g++)T=T*256+e[u+g];return T},d=(u)=>{if(u>=i)throw Error("Object reference out of range");return o(l+u*r,r)},c=new Set,h=(u)=>{if(c.has(u))throw Error("Cyclic property list");c.add(u);try{return y(d(u))}finally{c.delete(u)}},p=(u)=>{let v=e[u]&15;if(v!==15)return{length:v,start:u+1};let T=1<<(e[u+1]&15);return{length:o(u+2,T),start:u+2+T}},y=(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:w}=p(u);return e.slice(w,w+g)}case 5:{let{length:g,start:w}=p(u);return new TextDecoder("latin1").decode(e.subarray(w,w+g))}case 6:{let{length:g,start:w}=p(u),D="";for(let A=0;A<g;A++)D+=String.fromCharCode(t.getUint16(w+A*2));return D}case 8:return{uid:o(u+1,(v&15)+1)};case 10:{let{length:g,start:w}=p(u);return Array.from({length:g},(D,A)=>h(o(w+A*n,n)))}case 13:{let{length:g,start:w}=p(u),D={};for(let A=0;A<g;A++){let yt=h(o(w+A*n,n));D[String(yt)]=h(o(w+(g+A)*n,n))}return D}default:throw Error(`Unsupported property list object 0x${v.toString(16)}`)}};return h(a)}function k(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 st(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function he(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 rt=new TextEncoder,Nt=new TextDecoder("utf-8",{fatal:!1}),Ut=[rt.encode("NSString"),rt.encode("NSMutableString")];function $t(e,t,s=0){e:for(let r=s;r<=e.length-t.length;r++){for(let n=0;n<t.length;n++)if(e[r+n]!==t[n])continue e;return r}return-1}function Ft(e,t){let s=e[t];if(s===void 0)return null;let r=new DataView(e.buffer,e.byteOffset,e.byteLength);if(s===129){if(t+3>e.length)return null;return{length:r.getUint16(t+1,!0),start:t+3}}if(s===130){if(t+5>e.length)return null;return{length:r.getUint32(t+1,!0),start:t+5}}return{length:s,start:t+1}}function pe(e){if(!e||e.length===0)return null;for(let t of Ut){let s=$t(e,t);if(s===-1)continue;let r=s+t.length,n=Math.min(e.length,r+16);for(let i=r;i<n;i++){if(e[i]!==43)continue;let a=Ft(e,i+1);if(!a||a.start+a.length>e.length)continue;return Nt.decode(e.subarray(a.start,a.start+a.length))}}return null}var Ie=at(it(),"Library","Messages","chat.db"),ot=978307200,Wt=45,K=43;function U(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+ot)*1000)}function jt(e){return Math.round((e/1000-ot)*1e9)}function nt(e,t){let r=(e?.trim()?e:pe(t))?.replace(/\uFFFC/g,"").trim();return r?r:null}function Bt(e){if(!e||e.length===0)return null;try{let t=tt(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 ge extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t),r=/authorization denied|not permitted|unable to open/i.test(s);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=r}}var Zt=(e)=>e>=2000&&e<4000;function Re(e){if(!e)return null;return e.startsWith("~/")?at(it(),e.slice(2)):e}function Ht(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class De{path;db;messageColumns;hasRecoverable;constructor(e=Ie){this.path=e;try{this.db=new Lt(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new ge(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 Re(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 St=Object.defineProperty;var At=(e)=>e;function xt(e,t){this[e]=At.bind(null,t)}var ae=(e,t)=>{for(var s in t)St(e,s,{get:t[s],enumerable:!0,configurable:!0,set:xt.bind(t,s)})};var Ae={};ae(Ae,{DEFAULT_ALLOWED_MENTIONS:()=>Je,DiscordDriver:()=>L,Driver:()=>L,configure:()=>F,default:()=>_t,discordPayload:()=>oe,driver:()=>Mt,resolveConfig:()=>Ve,send:()=>le,sendEmbed:()=>Ke,sendWebhook:()=>W});var ke={};ae(ke,{Driver:()=>B,SlackDriver:()=>B,configure:()=>j,default:()=>Tt,driver:()=>Et,escapeSlackText:()=>ce,resolveConfig:()=>Ye,send:()=>ue,sendWebhook:()=>de});var Te={};ae(Te,{Driver:()=>H,TeamsDriver:()=>H,assertWebhookUrl:()=>N,configure:()=>Z,default:()=>It,driver:()=>Rt,resolveConfig:()=>Xe,send:()=>me,sendCard:()=>Qe,sendWebhook:()=>he});var vt={};ae(vt,{DiscordInboxDriver:()=>V,IMessageDriver:()=>X,SlackApiError:()=>ee,SlackInboxDriver:()=>te,WhatsAppDriver:()=>ie,createInboxDriver:()=>_s,decodeAttributedBody:()=>fe,formatHandle:()=>ge,formatJid:()=>Ge,normalizeHandle:()=>k,snowflakeTime:()=>Ie});import{log as Se}from"@stacksjs/logging";async function P(){let{awaitConfig:e}=await import("@stacksjs/config");return(await e()).services??{}}function _(e){return(e==null?"":String(e).trim())||void 0}function M(e){let t=Number(e);return e!==""&&e!=null&&Number.isFinite(t)&&t>=0?t:void 0}import{log as kt}from"@stacksjs/logging";class C{config;constructor(e){this.config={...e,maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000}}configure(e){this.config={...this.config,...e}}validateMessage(e){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));kt.error(`[${this.name}] Message sending failed`,{error:s.message,stack:s.stack?.replace(/https?:\/\/[^\s)\]"']+/g,"[REDACTED-URL]"),to:t.to,subject:t.subject});let r={message:`Message sending failed: ${s.message}`,success:!1,provider:this.name};if(t.onError){let n=t.onError(s),i=n instanceof Promise?await n:n;r={...r,...i,success:!1,provider:this.name}}return r}async handleSuccess(e,t){let s={message:"Message sent successfully",success:!0,provider:this.name,messageId:t};try{if(e.handle){let r=e.handle(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}if(e.onSuccess){let r=e.onSuccess(),n=r instanceof Promise?await r:r;s={...s,...n,success:!0,provider:this.name,messageId:t}}}catch(r){return this.handleError(r,e)}return s}}var R={},Je={parse:["users","roles"]};function oe(e){return{...e.content!==void 0?{content:e.content}:{},...e.username?{username:e.username}:{},...e.avatarUrl?{avatar_url:e.avatarUrl}:{},...e.tts!==void 0?{tts:e.tts}:{},...e.embeds?.length?{embeds:e.embeds}:{},allowed_mentions:e.allowedMentions??Je}}function ze(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/discord] webhookUrl is not a valid URL: ${e}`)}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.`)}function F(e){if(e.webhookUrl)ze(e.webhookUrl);R={...R,...e}}async function Ve(){let e={};try{e=(await P()).discord??{}}catch(s){Se.debug(`[chat/discord] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=R.webhookUrl??_(e.webhookUrl);if(t&&!R.webhookUrl)ze(t);return{webhookUrl:t,botToken:R.botToken??_(e.botToken),maxRetries:R.maxRetries??M(e.maxRetries),retryTimeout:R.retryTimeout??M(e.retryTimeout)}}class L extends C{name="discord";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)F(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Se.info("Sending message via Discord...",s);try{return this.validateMessage(e),this.settings=await Ve(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.id)}catch(r){if(s<this.config.maxRetries)return Se.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(this.settings.webhookUrl)return this.sendViaWebhook(e);if(this.settings.botToken)return this.sendViaBotToken(e);throw Error("Discord not configured: provide webhookUrl or botToken")}messageFor(e){return{...Boolean(e.subject||e.template)?{embeds:[this.buildEmbed(e)]}:{content:e.content},username:e.from?.name}}async sendViaWebhook(e){let t=await fetch(this.settings.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(oe(this.messageFor(e)))});if(!t.ok){let r=await t.text();throw Error(`Discord webhook failed: ${t.status} - ${r}`)}if(t.status===204)return{};return{id:(await t.json()).id}}async sendViaBotToken(e){let t=Array.isArray(e.to)?e.to[0]:e.to;if(!t)throw Error("Discord bot-token sends need message.to: the channel ID to post in");let{username:s,...r}=this.messageFor(e),n=await fetch(`https://discord.com/api/v10/channels/${t}/messages`,{method:"POST",headers:{Authorization:`Bot ${this.settings.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(oe(r))});if(!n.ok){let a=await n.json();throw Error(`Discord API failed: ${n.status} - ${a.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 le(e,t){return new L().send(e,t)}async function W(e,t,s){try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(oe({content:t,...s}))});if(!r.ok){let n=await r.text();return{success:!1,provider:"discord",message:`Webhook failed: ${r.status} - ${n}`}}return{success:!0,provider:"discord",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"discord",message:(r instanceof Error?r:Error(String(r))).message}}}async function Ke(e,t,s){return W(e,"",{...s,embeds:[t]})}var Mt=new L,_t={send:le,sendWebhook:W,sendEmbed:Ke,configure:F,DiscordDriver:L};import{log as xe}from"@stacksjs/logging";function ce(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}var I={};function qe(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/slack] webhookUrl is not a valid URL: ${e}`)}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.`)}function j(e){if(e.webhookUrl)qe(e.webhookUrl);I={...I,...e}}async function Ye(){let e={};try{e=(await P()).slack??{}}catch(s){xe.debug(`[chat/slack] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=I.webhookUrl??_(e.webhookUrl);if(t&&!I.webhookUrl)qe(t);return{webhookUrl:t,botToken:I.botToken??_(e.botToken),maxRetries:I.maxRetries??M(e.maxRetries),retryTimeout:I.retryTimeout??M(e.retryTimeout)}}class B extends C{name="slack";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)j(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};xe.info("Sending message via Slack...",s);try{return this.validateMessage(e),this.settings=await Ye(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{let r=await this.sendMessage(e,t);return this.handleSuccess(e,r.ts)}catch(r){if(s<this.config.maxRetries)return xe.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){if(this.settings.webhookUrl)return this.sendViaWebhook(e);if(this.settings.botToken)return this.sendViaBotToken(e);throw Error("Slack not configured: provide webhookUrl or botToken")}async sendViaWebhook(e){let s=ce(e.content||e.subject||""),n={text:s.length>40000?`${s.slice(0,39986)}\u2026 [truncated]`:s,username:e.from?.name,mrkdwn:!0};if(e.template)n.blocks=this.buildBlocks(e);let i=await fetch(this.settings.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){if(!e.to)throw Error("Slack bot-token sends need message.to: the channel to post in");let t={channel:e.to,text:ce(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 ${this.settings.botToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok)throw Error(`Slack API failed: ${s.status}`);let r=await s.json();if(!r.ok)throw Error(`Slack API error: ${r.error}`);return{ts:r.ts}}buildBlocks(e){let t=[];if(e.subject)t.push({type:"header",text:{type:"plain_text",text:e.subject,emoji:!0}});if(e.content)t.push({type:"section",text:{type:"mrkdwn",text:ce(e.content)}});return t}}async function ue(e,t){return new B().send(e,t)}async function de(e,t,s){try{let r={text:t,...s},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"slack",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"slack",message:"Message sent successfully"}}catch(r){return{success:!1,provider:"slack",message:(r instanceof Error?r:Error(String(r))).message}}}var Et=new B,Tt={send:ue,sendWebhook:de,configure:j,SlackDriver:B};import{log as Me}from"@stacksjs/logging";var O={},Ct=/(?:^|\.)(?:api\.powerplatform\.com|api\.powerautomate\.com|flow\.microsoft\.com|logic\.azure\.(?:com|us|cn))$/i;function N(e){let t;try{t=new URL(e)}catch{throw Error(`[chat/teams] webhookUrl is not a valid URL: ${e}`)}if(t.protocol!=="https:")throw Error("[chat/teams] webhookUrl must use https://");if(/(?:^|\.)webhook\.office\.com$/i.test(t.hostname))throw Error('[chat/teams] webhookUrl is an Office 365 connector webhook, which Microsoft retired in May 2026 and no longer delivers. Create one with the Workflows app in Teams ("Post to a channel when a webhook request is received") and use its URL.');if(!Ct.test(t.hostname))throw Error(`[chat/teams] webhookUrl host "${t.hostname}" is not a Teams Workflows (Power Automate) host.`)}function Z(e){if(e.webhookUrl)N(e.webhookUrl);O={...O,...e}}async function Xe(){let e={};try{e=(await P()).teams??{}}catch(s){Me.debug(`[chat/teams] could not read config/services.ts: ${s instanceof Error?s.message:String(s)}`)}let t=O.webhookUrl??_(e.webhookUrl);if(t&&!O.webhookUrl)N(t);return{webhookUrl:t,maxRetries:O.maxRetries??M(e.maxRetries),retryTimeout:O.retryTimeout??M(e.retryTimeout)}}class H extends C{name="teams";settings={};explicitRetries;constructor(e){super({maxRetries:e?.maxRetries??3,retryTimeout:e?.retryTimeout??1000});if(this.explicitRetries={maxRetries:e?.maxRetries,retryTimeout:e?.retryTimeout},e)Z(e)}async send(e,t){let s={provider:this.name,to:e.to,subject:e.subject||"No subject"};Me.info("Sending message via Microsoft Teams...",s);try{return this.validateMessage(e),this.settings=await Xe(),this.config={...this.config,maxRetries:this.explicitRetries.maxRetries??this.settings.maxRetries??this.config.maxRetries,retryTimeout:this.explicitRetries.retryTimeout??this.settings.retryTimeout??this.config.retryTimeout},await this.sendWithRetry(e,t)}catch(r){return this.handleError(r,e)}}async sendWithRetry(e,t,s=1){try{return await this.sendMessage(e,t),this.handleSuccess(e)}catch(r){if(s<this.config.maxRetries)return Me.warn(`[${this.name}] Message send failed, retrying (${s}/${this.config.maxRetries})`),await new Promise((n)=>setTimeout(n,this.config.retryTimeout)),this.sendWithRetry(e,t,s+1);throw r}}async sendMessage(e,t){let r=this.settings.webhookUrl||(Array.isArray(e.to)?e.to[0]:e.to);if(!r)throw Error("Teams webhook URL not configured");N(r);let n=this.buildPayload(e),i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){let a=await i.text();throw Error(`Teams webhook failed: ${i.status} - ${a}`)}}buildPayload(e){return Ee(this.buildAdaptiveCard(e),e.subject||e.content?.substring(0,50)||"Notification")}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});if(!e.subject&&!e.template)return _e(t);return t.push({type:"TextBlock",text:new Date().toLocaleString(),size:"Small",color:"Dark",wrap:!0}),_e(t)}}function _e(e){return{type:"AdaptiveCard",version:"1.4",$schema:"http://adaptivecards.io/schemas/adaptive-card.json",body:e}}function Ee(e,t){return{type:"message",summary:t,attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:e}]}}async function me(e,t){return new H().send(e,t)}async function he(e,t){try{N(e);let s=Ee(_e([{type:"TextBlock",text:t,wrap:!0}]),t.substring(0,50)||"Notification"),r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok){let n=await r.text();return{success:!1,provider:"teams",message:`Webhook failed: ${r.status} - ${n}`}}return{success:!0,provider:"teams",message:"Message sent successfully"}}catch(s){return{success:!1,provider:"teams",message:(s instanceof Error?s:Error(String(s))).message}}}async function Qe(e,t,s){try{N(e);let r=Ee(t,s||"Notification"),n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!n.ok){let i=await n.text();return{success:!1,provider:"teams",message:`Webhook failed: ${n.status} - ${i}`}}return{success:!0,provider:"teams",message:"Card sent successfully"}}catch(r){return{success:!1,provider:"teams",message:(r instanceof Error?r:Error(String(r))).message}}}var Rt=new H,It={send:me,sendWebhook:he,sendCard:Qe,configure:Z,TeamsDriver:H};var x={DISPATCH:0,HEARTBEAT:1,IDENTIFY:2,RESUME:6,RECONNECT:7,INVALID_SESSION:9,HELLO:10,HEARTBEAT_ACK:11},Dt=new Set([4004,4010,4011,4012,4013,4014]);class Ce{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 r=setTimeout(()=>s(Error("Discord did not answer in time.")),e);this.readyResolvers.push((n)=>{clearTimeout(r),t(n)})})}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 x.HELLO:if(this.startHeartbeat(s.d?.heartbeat_interval??41250),this.sessionId)this.send({op:x.RESUME,d:{token:this.identity.token,session_id:this.sessionId,seq:this.sequence}});else this.identify();break;case x.HEARTBEAT:this.send({op:x.HEARTBEAT,d:this.sequence});break;case x.HEARTBEAT_ACK:this.acknowledged=!0;break;case x.RECONNECT:e.close();break;case x.INVALID_SESSION:if(!s.d)this.sessionId=null,this.sequence=null;setTimeout(()=>this.sessionId?e.close():this.identify(),1000+Math.random()*4000);break;case x.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 r of this.readyResolvers.splice(0))r(s.d)}if(s.t)this.options.onDispatch(s.t,s.d);break}}identify(){this.send({op:x.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:x.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(Dt.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 r=Math.min(60000,1000*2**Math.min(this.failures++,6));setTimeout(()=>this.connect(),r)}}var Pt=1n<<10n,Ot=1n<<16n,Nt=1n<<3n,E=(e)=>BigInt(e??0);function et(e){if(e.ownerId&&e.ownerId===e.userId)return~0n;let t=new Map(e.roles.map((o)=>[o.id,o])),s=E(t.get(e.guildId)?.permissions);for(let o of e.memberRoles)s|=E(t.get(o)?.permissions);if(s&Nt)return~0n;let r=s,n=e.overwrites.find((o)=>o.id===e.guildId);if(n)r=r&~E(n.deny)|E(n.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|=E(o.allow),a|=E(o.deny);r=r&~a|i;let l=e.overwrites.find((o)=>(o.type===1||o.type==="member")&&o.id===e.userId);if(l)r=r&~E(l.deny)|E(l.allow);return r}function tt(e){return(e&Pt)!==0n&&(e&Ot)!==0n}function Ut(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 G(e){if(!e||e.length===0)return new Response("Not found",{status:404});return new Response(Uint8Array.from(e),{headers:{"content-type":Ut(e)}})}function pe(e,t){try{let s=new URL(e);return s.protocol==="https:"&&t.some((r)=>s.hostname===r||s.hostname.endsWith(`.${r}`))}catch{return!1}}var st=new Set([0,5]),J=new Set([10,11,12]),y=1,S=3,Ft={os:"Mac OS X",browser:"Attic",device:"Attic"};function Ie(e){if(!e)return null;return Number((BigInt(e)>>22n)+1420070400000n)}var rt=(e,t)=>!!e&&(!t||BigInt(e)>BigInt(t)),Re=(e)=>e.avatar?`https://cdn.discordapp.com/avatars/${e.id}/${e.avatar}.png?size=128`:null,z=(e)=>e.global_name||e.username;class V{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;previews=new Map;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}}async api(e,t={}){for(let s=0;;s++){let r=typeof t.body==="string",n=await this.fetch(`${this.apiBase}${e}`,{...t,headers:{...this.headers(),...r?{"content-type":"application/json"}:{},...t.headers??{}}});if(n.status===429&&s<3){let i=await n.json().catch(()=>({}));await Bun.sleep(Math.min(30,i.retry_after??1)*1000);continue}if(!n.ok){let i=await n.json().catch(()=>({}));throw Error(`Discord ${t.method??"GET"} ${e} failed: ${i.message??n.status}`)}return n.status===204?void 0:await n.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 ${z(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 Ce({token:this.token,properties:Ft,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 n of e.users??[])this.users.set(n.id,n);for(let n of e.private_channels??[]){for(let i of n.recipients??[])this.users.set(i.id,i);this.channels.set(n.id,n)}let t=Array.isArray(e.read_state)?e.read_state:e.read_state?.entries??[];for(let n of t)this.readStates.set(n.id,n);let s=Array.isArray(e.user_guild_settings)?e.user_guild_settings:e.user_guild_settings?.entries??[];for(let n of s){if(n.muted&&n.guild_id)this.muted.add(n.guild_id);for(let i of n.channel_overrides??[])if(i.muted)this.muted.add(i.channel_id)}(e.guilds??[]).forEach((n,i)=>{let a=n.properties?.name??n.name??"Server",l=n.properties?.icon??n.icon??null;this.guilds.set(n.id,a),this.icons.set(n.id,l?`https://cdn.discordapp.com/icons/${n.id}/${l}.png?size=128`:null);let o=e.merged_members?.[i]?.[0]?.roles??n.members?.find((c)=>c.user?.id===this.userId)?.roles??null,d=n.properties?.owner_id??n.owner_id??null;for(let c of[...n.channels??[],...n.threads??[]]){if(!st.has(c.type)&&!J.has(c.type))continue;let h={...c,guild_id:n.id};if(this.readStates.has(c.id)||o!==null&&tt(et({guildId:n.id,ownerId:d,userId:this.userId??"",memberRoles:o,roles:n.roles??[],overwrites:(J.has(c.type)?(n.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);this.previews.set(t.channel_id,{id:t.id,text:t.content||(t.attachments?.length?"Attachment":null),fromMe:t.author?.id===this.userId,at:Date.parse(t.timestamp)||Date.now()});let r=(t.mentions??[]).some((i)=>i.id===this.userId),n=this.muted.has(t.channel_id)||!!t.guild_id&&this.muted.has(t.guild_id);if(this.listed.has(t.channel_id)||s?.type===y||s?.type===S||!s&&!t.guild_id||r)if(n&&!r)this.emit({type:"changed",conversationId:t.channel_id});else 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===S){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===S)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===S)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===S)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 r of e){this.guilds.set(r.id,r.name),this.icons.set(r.id,r.icon?`https://cdn.discordapp.com/icons/${r.id}/${r.icon}.png?size=128`:null);let[n,i]=await Promise.all([this.api(`/guilds/${r.id}/channels`).catch(()=>[]),this.api(`/guilds/${r.id}/threads/active`).catch(()=>({threads:[]}))]);t.push(...n.filter((a)=>st.has(a.type)).map((a)=>({...a,guild_id:r.id}))),t.push(...i.threads.map((a)=>({...a,guild_id:a.guild_id??r.id})))}let s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,!0))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async userConversations(){if(!this.session?.ready)this.absorbReady(await this.gateway().whenReady());let e=[...this.channels.values()].filter((r)=>r.type===y||r.type===S||this.readStates.has(r.id)).sort((r,n)=>rt(r.last_message_id,n.last_message_id)?-1:1),t=new Set(e.filter((r)=>r.type===y||r.type===S).slice(0,40).map((r)=>r.id)),s=[];for(let r=0;r<e.length;r+=this.concurrency){let n=e.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,t.has(i.id)))))}return this.listed=new Set(s.map((r)=>r.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=this.previews.get(e.id);if(t&&e.last_message_id&&s?.id!==e.last_message_id){let p=(await this.api(`/channels/${e.id}/messages?limit=1`).catch(()=>[]))[0];if(p)s={id:p.id,text:p.content||(p.attachments?.length?"Attachment":null),fromMe:p.author.id===this.userId,at:Date.parse(p.timestamp)},this.previews.set(e.id,s)}if(s&&e.last_message_id&&s.id!==e.last_message_id)s=void 0;let r=J.has(e.type),n=e.type===y||e.type===S,i=n?this.recipients(e):[],a=e.type===y?i[0]?z(i[0]):"Direct message":e.type===S?e.name||i.map(z).join(", ")||"Group":r?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&&rt(e.last_message_id,l?.last_message_id??(n?null:e.last_message_id)),c=!this.isUser?0:l?.mention_count||(d&&(n||!o)?1:0),h=e.type===y?i[0]?Re(i[0]):null:e.type===S?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===S?"group":r?"thread":"channel",title:a,participants:i.map((p)=>({id:p.id,name:z(p),avatar:Re(p)})),workspace:e.guild_id?this.guilds.get(e.guild_id)??null:null,avatar:h,lastMessageAt:s?s.at:Ie(e.last_message_id),preview:s?.text??null,previewFromMe:!!s?.fromMe,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,r=[],n=(i,a)=>BigInt(i.id)<BigInt(a.id)?-1:1;if(t.after){let i=t.after;while(r.length<s){let a=await this.api(`/channels/${e}/messages?limit=100&after=${i}`);if(a.length===0)break;if(a.sort(n),r.push(...a),i=a[a.length-1].id,a.length<100)break}}else{let i="";while(r.length<s){let a=await this.api(`/channels/${e}/messages?limit=${Math.min(100,s-r.length)}${i?`&before=${i}`:""}`);if(a.length===0)break;if(a.sort(n),r.unshift(...a),i=a[0].id,a.length<100)break}}return r.slice(0,s).flatMap((i)=>this.toMessage(e,i))}toMessage(e,t){let s=t.author.id===this.userId,r=[{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:z(t.author),avatar:Re(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((n)=>({id:n.id,name:n.filename,mimeType:n.content_type??null,bytes:n.size,path:null,url:n.url})),cursor:t.id}];for(let n of t.reactions??[])r.push({id:`${t.id}:${n.emoji.id??n.emoji.name}`,conversationId:e,kind:"reaction",text:n.count>1?String(n.count):null,fromMe:!!n.me,sentAt:Date.parse(t.timestamp),sender:null,targetId:t.id,reaction:n.emoji.name??"\u2022",reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:t.id});return r}async send(e,t){let s=t.text?.trim()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=String(BigInt(Date.now())-1420070400000n<<22n),i={content:s,nonce:n,tts:!1};if(t.replyToId)i.message_reference={message_id:t.replyToId,channel_id:e};let a;if(r.length===0)a=await this.api(`/channels/${e}/messages`,{method:"POST",body:JSON.stringify(i)});else{let o=new FormData;i.attachments=r.map((d,c)=>({id:c,filename:d.name??d.path.split("/").pop()})),o.append("payload_json",JSON.stringify(i));for(let[d,c]of r.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,r=!1){let n=t.split(":")[0];await this.api(`/channels/${e}/messages/${n}/reactions/${encodeURIComponent(s)}/@me`,{method:r?"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(!pe(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 ye,readFileSync as es,statSync as ts}from"fs";import{dirname as ss}from"path";import rs from"process";import{Database as Bt}from"bun:sqlite";import{homedir as lt}from"os";import{join as ct}from"path";function nt(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,r=e[s+6],n=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 T=0;for(let g=0;g<v;g++)T=T*256+e[u+g];return T},d=(u)=>{if(u>=i)throw Error("Object reference out of range");return o(l+u*r,r)},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)}},p=(u)=>{let v=e[u]&15;if(v!==15)return{length:v,start:u+1};let T=1<<(e[u+1]&15);return{length:o(u+2,T),start:u+2+T}},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:w}=p(u);return e.slice(w,w+g)}case 5:{let{length:g,start:w}=p(u);return new TextDecoder("latin1").decode(e.subarray(w,w+g))}case 6:{let{length:g,start:w}=p(u),D="";for(let A=0;A<g;A++)D+=String.fromCharCode(t.getUint16(w+A*2));return D}case 8:return{uid:o(u+1,(v&15)+1)};case 10:{let{length:g,start:w}=p(u);return Array.from({length:g},(D,A)=>h(o(w+A*n,n)))}case 13:{let{length:g,start:w}=p(u),D={};for(let A=0;A<g;A++){let wt=h(o(w+A*n,n));D[String(wt)]=h(o(w+(g+A)*n,n))}return D}default:throw Error(`Unsupported property list object 0x${v.toString(16)}`)}};return h(a)}function k(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 it(e){return/^\+\d{7,15}$/.test(e)||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function ge(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 at=new TextEncoder,Lt=new TextDecoder("utf-8",{fatal:!1}),$t=[at.encode("NSString"),at.encode("NSMutableString")];function Wt(e,t,s=0){e:for(let r=s;r<=e.length-t.length;r++){for(let n=0;n<t.length;n++)if(e[r+n]!==t[n])continue e;return r}return-1}function jt(e,t){let s=e[t];if(s===void 0)return null;let r=new DataView(e.buffer,e.byteOffset,e.byteLength);if(s===129){if(t+3>e.length)return null;return{length:r.getUint16(t+1,!0),start:t+3}}if(s===130){if(t+5>e.length)return null;return{length:r.getUint32(t+1,!0),start:t+5}}return{length:s,start:t+1}}function fe(e){if(!e||e.length===0)return null;for(let t of $t){let s=Wt(e,t);if(s===-1)continue;let r=s+t.length,n=Math.min(e.length,r+16);for(let i=r;i<n;i++){if(e[i]!==43)continue;let a=jt(e,i+1);if(!a||a.start+a.length>e.length)continue;return Lt.decode(e.subarray(a.start,a.start+a.length))}}return null}var Pe=ct(lt(),"Library","Messages","chat.db"),ut=978307200,Zt=45,K=43;function U(e){if(!e)return null;let t=e>1000000000000?e/1e9:e;return Math.round((t+ut)*1000)}function Ht(e){return Math.round((e/1000-ut)*1e9)}function ot(e,t){let r=(e?.trim()?e:fe(t))?.replace(/\uFFFC/g,"").trim();return r?r:null}function Gt(e){if(!e||e.length===0)return null;try{let t=nt(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 be extends Error{path;needsFullDiskAccess;constructor(e,t){let s=t instanceof Error?t.message:String(t),r=/authorization denied|not permitted|unable to open/i.test(s);super(`Cannot read ${e}: ${s}`);this.path=e;this.name="MessagesAccessError",this.needsFullDiskAccess=r}}var Jt=(e)=>e>=2000&&e<4000;function De(e){if(!e)return null;return e.startsWith("~/")?ct(lt(),e.slice(2)):e}function zt(e){if(!e)return null;let t=e.indexOf("/");if(t!==-1)return e.slice(t+1);return e.replace(/^bp:/,"")}class Oe{path;db;messageColumns;hasRecoverable;constructor(e=Pe){this.path=e;try{this.db=new Bt(e,{readonly:!0}),this.db.query("SELECT 1 FROM message LIMIT 1").all()}catch(t){throw new be(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 De(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 bt=Object.defineProperty;var vt=(e)=>e;function wt(e,t){this[e]=vt.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(),s=this.participantsByChat(),r=this.db.query("SELECT m.text, m.attributedBody, m.date, m.is_from_me FROM message m WHERE m.ROWID = ?");return t.map((i)=>{let a=i.last_rowid?r.get(i.last_rowid):null;return{rowid:i.rowid,guid:i.guid,identifier:i.chat_identifier?k(i.chat_identifier):i.guid,style:i.style??Wt,service:i.service_name??"iMessage",displayName:i.display_name?.trim()||null,groupId:i.group_id||null,groupPhotoGuid:Bt(i.properties),participants:s.get(i.rowid)??[],messageCount:i.message_count,recoverableCount:i.recoverable_count,unreadCount:i.unread_count,lastMessageAt:a?U(a.date):null,lastMessageRowId:i.last_rowid??0,lastMessageText:a?nt(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(),r=this.db.query("SELECT m.text, m.attributedBody, m.date, m.is_from_me FROM message m WHERE m.ROWID = ?");return t.map((i)=>{let a=i.last_rowid?r.get(i.last_rowid):null;return{rowid:i.rowid,guid:i.guid,identifier:i.chat_identifier?k(i.chat_identifier):i.guid,style:i.style??Zt,service:i.service_name??"iMessage",displayName:i.display_name?.trim()||null,groupId:i.group_id||null,groupPhotoGuid:Gt(i.properties),participants:s.get(i.rowid)??[],messageCount:i.message_count,recoverableCount:i.recoverable_count,unreadCount:i.unread_count,lastMessageAt:a?U(a.date):null,lastMessageRowId:i.last_rowid??0,lastMessageText:a?ot(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 s of e){let r=t.get(s.chat_id)??[],n=k(s.id);if(!r.includes(n))r.push(n);t.set(s.chat_id,r)}return t}messages(e,t={}){if(e.length===0)return[];let s=e.map(()=>"?").join(", "),r=t.newestFirst?"m.date DESC, m.ROWID DESC":t.byRowId?"m.ROWID ASC":"m.date ASC, m.ROWID ASC",n=t.beforeMs?`AND m.date < ${jt(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 r=t.get(s.chat_id)??[],n=k(s.id);if(!r.includes(n))r.push(n);t.set(s.chat_id,r)}return t}messages(e,t={}){if(e.length===0)return[];let s=e.map(()=>"?").join(", "),r=t.newestFirst?"m.date DESC, m.ROWID DESC":t.byRowId?"m.ROWID ASC":"m.date ASC, m.ROWID ASC",n=t.beforeMs?`AND m.date < ${Ht(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,
@@ -29,14 +29,14 @@ var bt=Object.defineProperty;var vt=(e)=>e;function wt(e,t){this[e]=vt.bind(null
29
29
  WHERE c.guid IN (${s}) AND m.ROWID > ? ${n}
30
30
  ORDER BY ${r}
31
31
  LIMIT ?
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=Zt(d)?"reaction":c!==0?"event":"message";return{rowid:o.rowid,guid:o.guid,chatGuid:o.chat_guid,kind:h,text:nt(o.text,o.attributedBody),isFromMe:o.is_from_me===1,sentAt:U(o.date)??0,sender:o.sender?k(o.sender):null,service:o.service??"iMessage",targetGuid:h==="reaction"?Ht(o.associated_message_guid):null,reactionType:h==="reaction"?d:0,replyToGuid:o.thread_originator_guid||null,editedAt:U(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=Re(t.filename);return{path:s,mimeType:t.mime_type,name:t.transfer_name||(s?s.split("/").pop():e)}}attachmentsFor(e){let t=new Map;for(let s=0;s<e.length;s+=500){let r=e.slice(s,s+500),n=this.db.query(`
32
+ `).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=Jt(d)?"reaction":c!==0?"event":"message";return{rowid:o.rowid,guid:o.guid,chatGuid:o.chat_guid,kind:h,text:ot(o.text,o.attributedBody),isFromMe:o.is_from_me===1,sentAt:U(o.date)??0,sender:o.sender?k(o.sender):null,service:o.service??"iMessage",targetGuid:h==="reaction"?zt(o.associated_message_guid):null,reactionType:h==="reaction"?d:0,replyToGuid:o.thread_originator_guid||null,editedAt:U(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=De(t.filename);return{path:s,mimeType:t.mime_type,name:t.transfer_name||(s?s.split("/").pop():e)}}attachmentsFor(e){let t=new Map;for(let s=0;s<e.length;s+=500){let r=e.slice(s,s+500),n=this.db.query(`
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
35
  WHERE maj.message_id IN (${r.map(()=>"?").join(", ")})
36
- `).all(...r);for(let i of n){let a=t.get(i.message_id)??[],l=Re(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 lt}from"bun:sqlite";import{existsSync as ct,readdirSync as Gt,readFileSync as Jt}from"fs";import{homedir as zt}from"os";import{dirname as Vt,join as q}from"path";var Pe=q(zt(),"Library","Application Support","AddressBook");function ut(e,t){if(!t||t.length<2)return null;if(t[0]===1)return t.subarray(1);if(t[0]===2){let s=new TextDecoder().decode(t.subarray(1)).replace(/\0+$/,"").trim();if(!/^[\w-]+$/.test(s))return null;let r=q(Vt(e),".AddressBook-v22_SUPPORT","_EXTERNAL_DATA",s);try{return new Uint8Array(Jt(r))}catch{return null}}return null}function Kt(e){let t=[],s=q(e,"AddressBook-v22.abcddb");if(ct(s))t.push(s);let r=q(e,"Sources");try{for(let n of Gt(r)){let i=q(r,n,"AddressBook-v22.abcddb");if(ct(i))t.push(i)}}catch{}return t}function dt(e){let t=e.replace(/[^\p{L}\p{N}\s]/gu," ").trim().split(/\s+/).filter(Boolean);if(t.length===0)return"?";let s=t[0][0],r=t.length>1?t[t.length-1][0]:"";return(s+r).toUpperCase()}class Y{byHandle=new Map;static load(e=Pe){let t=new Y;for(let s of Kt(e))try{t.read(s)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new lt(e,{readonly:!0});try{let s=new Set(t.query("PRAGMA table_info(ZABCDRECORD)").all().map((c)=>c.name)),r=["ZTHUMBNAILIMAGEDATA","ZIMAGEDATA"].filter((c)=>s.has(c)),n=r.length>0?`(${r.map((c)=>`${c} IS NOT NULL`).join(" OR ")})`:"0",i=t.query(`
36
+ `).all(...r);for(let i of n){let a=t.get(i.message_id)??[],l=De(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 dt}from"bun:sqlite";import{existsSync as mt,readdirSync as Vt,readFileSync as Kt}from"fs";import{homedir as qt}from"os";import{dirname as Yt,join as q}from"path";var Ne=q(qt(),"Library","Application Support","AddressBook");function ht(e,t){if(!t||t.length<2)return null;if(t[0]===1)return t.subarray(1);if(t[0]===2){let s=new TextDecoder().decode(t.subarray(1)).replace(/\0+$/,"").trim();if(!/^[\w-]+$/.test(s))return null;let r=q(Yt(e),".AddressBook-v22_SUPPORT","_EXTERNAL_DATA",s);try{return new Uint8Array(Kt(r))}catch{return null}}return null}function Xt(e){let t=[],s=q(e,"AddressBook-v22.abcddb");if(mt(s))t.push(s);let r=q(e,"Sources");try{for(let n of Vt(r)){let i=q(r,n,"AddressBook-v22.abcddb");if(mt(i))t.push(i)}}catch{}return t}function pt(e){let t=e.replace(/[^\p{L}\p{N}\s]/gu," ").trim().split(/\s+/).filter(Boolean);if(t.length===0)return"?";let s=t[0][0],r=t.length>1?t[t.length-1][0]:"";return(s+r).toUpperCase()}class Y{byHandle=new Map;static load(e=Ne){let t=new Y;for(let s of Xt(e))try{t.read(s)}catch{}return t}get size(){return this.byHandle.size}read(e){let t=new dt(e,{readonly:!0});try{let s=new Set(t.query("PRAGMA table_info(ZABCDRECORD)").all().map((c)=>c.name)),r=["ZTHUMBNAILIMAGEDATA","ZIMAGEDATA"].filter((c)=>s.has(c)),n=r.length>0?`(${r.map((c)=>`${c} IS NOT NULL`).join(" OR ")})`:"0",i=t.query(`
37
37
  SELECT Z_PK AS id, ZFIRSTNAME AS first, ZLASTNAME AS last, ZNICKNAME AS nick, ZORGANIZATION AS org, ${n} AS hasPhoto
38
38
  FROM ZABCDRECORD
39
- `).all(),a=new Map,l=new Set;for(let c of i){let p=[c.first,c.last].filter(Boolean).join(" ").trim()||c.nick?.trim()||c.org?.trim();if(p)a.set(c.id,p);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 p=k(c.value),y=l.has(c.owner)?{file:e,record:c.owner}:null,u=this.byHandle.get(p);if(!u)this.byHandle.set(p,{name:h,initials:dt(h),photo:y});else if(!u.photo&&y)u.photo=y}}finally{t.close()}}set(e,t){this.byHandle.set(k(e),{name:t,initials:dt(t)})}lookup(e){return this.byHandle.get(k(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 lt(t.file,{readonly:!0});try{let r=s.query("SELECT ZTHUMBNAILIMAGEDATA AS thumb, ZIMAGEDATA AS full FROM ZABCDRECORD WHERE Z_PK = ?").get(t.record);return ut(t.file,r?.thumb??null)??ut(t.file,r?.full??null)}finally{s.close()}}catch{return null}}}function qt(e){if(e.style===K)return`group:${e.groupId||e.guid}`;return`direct:${e.identifier}`}function mt(e){let t=new Map;for(let s of e){let r=qt(s),n=t.get(r);if(!n){t.set(r,{key:r,kind:s.style===K?"group":"direct",chatGuids:[s.guid],displayName:s.displayName,groupId:s.groupId,groupPhotoGuid:s.style===K?s.groupPhotoGuid:null,participants:s.style===K?[...s.participants]:[s.identifier],messageCount:s.messageCount,recoverableCount:s.recoverableCount,unreadCount:s.unreadCount,lastMessageAt:s.lastMessageAt,lastMessageRowId:s.lastMessageRowId,lastMessageText:s.lastMessageText,lastMessageFromMe:s.lastMessageFromMe,services:[s.service]});continue}if(n.chatGuids.push(s.guid),n.kind==="group"&&!n.groupPhotoGuid)n.groupPhotoGuid=s.groupPhotoGuid;if(n.messageCount+=s.messageCount,n.recoverableCount+=s.recoverableCount,n.unreadCount+=s.unreadCount,!n.services.includes(s.service))n.services.push(s.service);for(let i of s.participants)if(n.kind==="group"&&!n.participants.includes(i))n.participants.push(i);if((s.lastMessageAt??0)>(n.lastMessageAt??0))n.lastMessageAt=s.lastMessageAt,n.lastMessageRowId=s.lastMessageRowId,n.lastMessageText=s.lastMessageText,n.lastMessageFromMe=s.lastMessageFromMe,n.displayName=s.displayName??n.displayName}return[...t.values()].sort((s,r)=>(r.lastMessageAt??0)-(s.lastMessageAt??0))}function Oe(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!st(t))return null;return`sms:${t}`}var ts=`on run argv
39
+ `).all(),a=new Map,l=new Set;for(let c of i){let p=[c.first,c.last].filter(Boolean).join(" ").trim()||c.nick?.trim()||c.org?.trim();if(p)a.set(c.id,p);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 p=k(c.value),b=l.has(c.owner)?{file:e,record:c.owner}:null,u=this.byHandle.get(p);if(!u)this.byHandle.set(p,{name:h,initials:pt(h),photo:b});else if(!u.photo&&b)u.photo=b}}finally{t.close()}}set(e,t){this.byHandle.set(k(e),{name:t,initials:pt(t)})}lookup(e){return this.byHandle.get(k(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 dt(t.file,{readonly:!0});try{let r=s.query("SELECT ZTHUMBNAILIMAGEDATA AS thumb, ZIMAGEDATA AS full FROM ZABCDRECORD WHERE Z_PK = ?").get(t.record);return ht(t.file,r?.thumb??null)??ht(t.file,r?.full??null)}finally{s.close()}}catch{return null}}}function Qt(e){if(e.style===K)return`group:${e.groupId||e.guid}`;return`direct:${e.identifier}`}function gt(e){let t=new Map;for(let s of e){let r=Qt(s),n=t.get(r);if(!n){t.set(r,{key:r,kind:s.style===K?"group":"direct",chatGuids:[s.guid],displayName:s.displayName,groupId:s.groupId,groupPhotoGuid:s.style===K?s.groupPhotoGuid:null,participants:s.style===K?[...s.participants]:[s.identifier],messageCount:s.messageCount,recoverableCount:s.recoverableCount,unreadCount:s.unreadCount,lastMessageAt:s.lastMessageAt,lastMessageRowId:s.lastMessageRowId,lastMessageText:s.lastMessageText,lastMessageFromMe:s.lastMessageFromMe,services:[s.service]});continue}if(n.chatGuids.push(s.guid),n.kind==="group"&&!n.groupPhotoGuid)n.groupPhotoGuid=s.groupPhotoGuid;if(n.messageCount+=s.messageCount,n.recoverableCount+=s.recoverableCount,n.unreadCount+=s.unreadCount,!n.services.includes(s.service))n.services.push(s.service);for(let i of s.participants)if(n.kind==="group"&&!n.participants.includes(i))n.participants.push(i);if((s.lastMessageAt??0)>(n.lastMessageAt??0))n.lastMessageAt=s.lastMessageAt,n.lastMessageRowId=s.lastMessageRowId,n.lastMessageText=s.lastMessageText,n.lastMessageFromMe=s.lastMessageFromMe,n.displayName=s.displayName??n.displayName}return[...t.values()].sort((s,r)=>(r.lastMessageAt??0)-(s.lastMessageAt??0))}function Ue(e){if(e.kind==="group")return e.groupId?`sms://open?groupid=${encodeURIComponent(e.groupId)}`:null;let t=e.participants[0];if(!t||!it(t))return null;return`sms:${t}`}var ns=`on run argv
40
40
  set chatId to item 1 of argv
41
41
  set theHandle to item 2 of argv
42
42
  set serviceName to item 3 of argv
@@ -57,7 +57,7 @@ var bt=Object.defineProperty;var vt=(e)=>e;function wt(e,t){this[e]=vt.bind(null
57
57
  send (POSIX file (item i of argv)) to target
58
58
  end repeat
59
59
  end tell
60
- end run`;async function ss(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...t],{stdout:"pipe",stderr:"pipe"}),[r,n,i]=await Promise.all([new Response(s.stdout).text(),new Response(s.stderr).text(),s.exited]);return{ok:i===0,output:(i===0?r:n).trim()}}var rs={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function ns(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}class X{provider="imessage";label="iMessage";databasePath;addressBookDir;openUrl;controller;confirmDeletes;platform;runScript;contacts=null;contactsLoadedAt=0;constructor(e={}){this.databasePath=e.databasePath??Ie,this.addressBookDir=e.addressBookDir??Pe,this.openUrl=e.openUrl??ns,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??es.platform,this.runScript=e.runScript??ss}capabilities(){return{send:!0,attachments:!0,replies:!1,reactions:!1,markRead:!1,live:!1}}async send(e,t){let s=t.text?.trim()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=this.open((a)=>{let l=this.find(a,e);if(!l)return null;let[o]=a.messages(l.chatGuids,{newestFirst:!0,limit:1});return{chatGuid:o?.chatGuid??l.chatGuids[0]??"",handle:l.kind==="direct"?l.participants[0]??"":"",service:o?.service==="SMS"?"SMS":"iMessage"}});if(!n||!n.chatGuid&&!n.handle)throw Error("Messages has no chat for this conversation.");let i=await this.runScript(ts,[n.chatGuid,n.handle,n.service,s,...r.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]),he(e.participants[0]),e.participants[0]);return[...new Set(t.filter((s)=>!!s))]}open(e){let t=new De(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new Y;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=Y.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 G(t&&fe(t)?new Uint8Array(Yt(t)):null)}return G(this.names().photo(e))}grouped=null;chats(e){let t=[this.databasePath,`${this.databasePath}-wal`].map((s)=>{try{let r=Xt(s);return`${r.mtimeMs}:${r.size}`}catch{return"-"}}).join("|");if(this.grouped?.stamp!==t)this.grouped={stamp:t,chats:mt(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 ge&&e.needsFullDiskAccess&&fe(Qt(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=Oe(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 r=this.find(s,e);if(!r)return[];let n=t.includeDeleted;return(t.after||!t.limit?s.messages(r.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:n}):s.messages(r.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:n}).reverse()).map((a)=>this.toMessage(e,a))})}toMessage(e,t){let s=t.reactionType>=3000;return{id:t.guid,conversationId:e,kind:t.kind,text:t.kind==="event"&&t.groupTitle?`Named the conversation \u201C${t.groupTitle}\u201D`:t.text,fromMe:t.isFromMe,sentAt:t.sentAt,sender:t.isFromMe?null:this.person(t.sender),targetId:t.targetGuid,reaction:t.kind==="reaction"?rs[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((r)=>({id:r.guid,name:r.name,mimeType:r.mimeType,bytes:r.bytes,path:r.path&&fe(r.path)?r.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((n)=>this.find(n,e)),s=t?Oe(t):null;if(!t||!s)return{mode:"unsupported",removed:!1,detail:"Messages cannot open this conversation by address, so delete it there yourself."};if(t.messageCount===0)return{mode:"confirm",removed:!0,detail:"Already gone from Messages."};let r=this.rowNames(t);if(this.controller&&r.length>0){let n=await this.controller.remove(r,s,this.confirmDeletes);return{mode:"confirm",removed:!1,detail:n.ok?this.confirmDeletes?"Deleted in Messages.":"Messages is asking you to confirm the delete.":n.detail}}return this.openUrl(s),{mode:"confirm",removed:!1,detail:"Messages is open on the conversation: choose Conversation > Delete Conversation to finish."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>this.find(r,e));if(!t||t.messageCount>0)return;let s=this.rowNames(t);if(s.length>0)await this.controller.recover(s)}async attachment(e){if(!e.path||!fe(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 is="im,mpim,public_channel,private_channel",ht={"+1":"\uD83D\uDC4D",thumbsup:"\uD83D\uDC4D","-1":"\uD83D\uDC4E",heart:"\u2764\uFE0F",joy:"\uD83D\uDE02",laughing:"\uD83D\uDE06",tada:"\uD83C\uDF89",eyes:"\uD83D\uDC40",fire:"\uD83D\uDD25",white_check_mark:"\u2705",pray:"\uD83D\uDE4F",raised_hands:"\uD83D\uDE4C",rocket:"\uD83D\uDE80"},Q=(e)=>Math.round(Number(e)*1000);function as(e){let t=Object.entries(ht).find(([,r])=>r===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 ee extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class te{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()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=t.replyToId?await this.threadOf(e,t.replyToId):null;if(r.length===0){let a=await this.api("chat.postMessage",{channel:e,text:s,...n?{thread_ts:n}:{}},!0);return{id:a.ts,sentAt:Q(a.ts)}}let i=[];for(let a of r){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}:{},...n?{thread_ts:n}:{}},!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,r=!1){let n=as(s),i=t.split(":")[0];await this.api(r?"reactions.remove":"reactions.add",{channel:e,timestamp:i,name:n},!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,r=null,n=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 ee("apps.connections.open",l.error??`http_${a.status}`);let o=new t(l.url);r=o;let d=Promise.resolve();o.onmessage=(c)=>{n=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(r=null,!s)setTimeout(i,Math.min(60000,1000*2**Math.min(n++,6)))}}catch{if(!s)setTimeout(i,Math.min(60000,1000*2**Math.min(n++,6)))}};return i(),()=>{s=!0,r?.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 n=await this.toMessage(s.channel,s);t({type:"message",conversationId:s.channel,message:n});return}let r=s.channel??s.item?.channel??null;t({type:"changed",conversationId:r})}async api(e,t={},s=!1){for(let r=0;;r++){let n=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:n.toString()}):await this.fetch(`${this.apiBase}/${e}?${n}`,{headers:{authorization:`Bearer ${this.token}`}});if(i.status===429&&r<3){await Bun.sleep(Math.min(30,Number(i.headers.get("retry-after"))||1)*1000);continue}let a=await i.json();if(!a.ok)throw new ee(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(!me(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:is,exclude_archived:!0,limit:200,...t?{cursor:t}:{}});e.push(...s.channels),t=s.response_metadata?.next_cursor??""}while(t);return e}async conversations(){let e=await this.whoami(),t=await this.list(),s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,e))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e,t){let[{channel:s},r]=await Promise.all([this.api("conversations.info",{channel:e.id}).catch(()=>({channel:e})),this.api("conversations.history",{channel:e.id,limit:1}).catch(()=>({messages:[]}))]),n={...e,...s};this.channels.set(n.id,n);let i=r.messages[0],a=[];if(n.is_im)a=[await this.person(n.user)];else if(n.is_mpim){let l=await this.api("conversations.members",{channel:n.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: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?Q(i.ts):null,preview:i?.text||(i?.files?.length?"Attachment":null),previewFromMe:i?.user===t.userId,unread:n.unread_count_display??0,cursor:i?.ts??"0",visible:n.is_im||n.is_mpim?n.is_open!==!1:!0,archive:this.archiveSupport(n),url:`slack://channel?team=${t.teamId}&id=${n.id}`}}async messages(e,t={}){let s=await this.whoami(),r=[],n="",i=t.limit??1000;do{let l=await this.api("conversations.history",{channel:e,limit:Math.min(200,i),...t.after?{oldest:t.after,inclusive:!1}:{},...n?{cursor:n}:{}});r.push(...l.messages),n=l.has_more?l.response_metadata?.next_cursor??"":""}while(n&&r.length<i);r.sort((l,o)=>Number(l.ts)-Number(o.ts));let a=[];for(let l of r.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:Q(l.ts),sender:d===s.userId?null:await this.person(d),targetId:l.ts,reaction:ht[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(),r=await this.person(t.user),n=!!t.subtype&&!["file_share","thread_broadcast","me_message"].includes(t.subtype);return{id:t.ts,conversationId:e,kind:n?"event":"message",text:t.text??null,fromMe:t.user===s.userId,sentAt:Q(t.ts),sender:t.user===s.userId?null:r??(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?Q(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 be,readFileSync as hs}from"fs";import{basename as ps,dirname as je,extname as gs,join as Be}from"path";import fs from"process";function Ne(e,t){let s=0n,r=0n;for(let n=t;n<e.length&&n<t+10;n++){let i=e[n];if(s|=BigInt(i&127)<<r,(i&128)===0)return{value:s,next:n+1};r+=7n}throw Error("Truncated varint")}function os(e){let t=[],s=0;while(s<e.length){let r=Ne(e,s);s=r.next;let n=Number(r.value>>3n),i=Number(r.value&7n);if(n===0)throw Error("Field number 0");if(i===0){let a=Ne(e,s);t.push({field:n,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:n,wire:i,value:l}),s+=a}else if(i===2){let a=Ne(e,s),l=a.next,o=l+Number(a.value);if(o>e.length)throw Error("Truncated length-delimited field");t.push({field:n,wire:i,value:e.subarray(l,o)}),s=o}else throw Error(`Unsupported wire type ${i}`)}return t}function se(e){if(!e||e.length===0)return null;try{return os(e)}catch{return null}}function ls(e,t){let s=e?.find((r)=>r.field===t&&r.wire===2);return s?s.value:null}function Ue(e,t){return(e??[]).filter((s)=>s.field===t&&s.wire===2).map((s)=>s.value)}function re(e,t){let s=ls(e,t);if(!s)return null;let r=new TextDecoder("utf-8",{fatal:!0});try{return r.decode(s)}catch{return null}}function $e(e,t){let s=e?.find((r)=>r.field===t&&r.wire!==2);return s?s.value:null}import{Database as cs}from"bun:sqlite";import{homedir as us}from"os";import{join as Fe}from"path";var pt=Fe(us(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),Le=Fe(pt,"ChatStorage.sqlite"),gt=Fe(pt,"Message"),ne=0,ds=1,ms=4,m={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 ye 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 We{path;db;constructor(e=Le){this.path=e;try{this.db=new cs(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new ye(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(`
60
+ end run`;async function is(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...t],{stdout:"pipe",stderr:"pipe"}),[r,n,i]=await Promise.all([new Response(s.stdout).text(),new Response(s.stderr).text(),s.exited]);return{ok:i===0,output:(i===0?r:n).trim()}}var as={2000:"\u2764\uFE0F",2001:"\uD83D\uDC4D",2002:"\uD83D\uDC4E",2003:"\uD83D\uDE02",2004:"\u203C\uFE0F",2005:"\u2753",2006:"\uD83D\uDE00",2007:"\uD83D\uDD16"};function os(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}class X{provider="imessage";label="iMessage";databasePath;addressBookDir;openUrl;controller;confirmDeletes;platform;runScript;contacts=null;contactsLoadedAt=0;constructor(e={}){this.databasePath=e.databasePath??Pe,this.addressBookDir=e.addressBookDir??Ne,this.openUrl=e.openUrl??os,this.controller=e.controller,this.confirmDeletes=e.confirmDeletes??!1,this.platform=e.platform??rs.platform,this.runScript=e.runScript??is}capabilities(){return{send:!0,attachments:!0,replies:!1,reactions:!1,markRead:!1,live:!1}}async send(e,t){let s=t.text?.trim()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=this.open((a)=>{let l=this.find(a,e);if(!l)return null;let[o]=a.messages(l.chatGuids,{newestFirst:!0,limit:1});return{chatGuid:o?.chatGuid??l.chatGuids[0]??"",handle:l.kind==="direct"?l.participants[0]??"":"",service:o?.service==="SMS"?"SMS":"iMessage"}});if(!n||!n.chatGuid&&!n.handle)throw Error("Messages has no chat for this conversation.");let i=await this.runScript(ns,[n.chatGuid,n.handle,n.service,s,...r.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]),ge(e.participants[0]),e.participants[0]);return[...new Set(t.filter((s)=>!!s))]}open(e){let t=new Oe(this.databasePath);try{return e(t)}finally{t.close()}}names(){if(this.addressBookDir===!1)return this.contacts??=new Y;if(!this.contacts||Date.now()-this.contactsLoadedAt>600000)this.contacts=Y.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 G(t&&ye(t)?new Uint8Array(es(t)):null)}return G(this.names().photo(e))}grouped=null;chats(e){let t=[this.databasePath,`${this.databasePath}-wal`].map((s)=>{try{let r=ts(s);return`${r.mtimeMs}:${r.size}`}catch{return"-"}}).join("|");if(this.grouped?.stamp!==t)this.grouped={stamp:t,chats:gt(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 be&&e.needsFullDiskAccess&&ye(ss(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=Ue(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 r=this.find(s,e);if(!r)return[];let n=t.includeDeleted;return(t.after||!t.limit?s.messages(r.chatGuids,{afterRowId:Number(t.after)||0,byRowId:!0,limit:t.limit,includeDeleted:n}):s.messages(r.chatGuids,{newestFirst:!0,limit:t.limit,includeDeleted:n}).reverse()).map((a)=>this.toMessage(e,a))})}toMessage(e,t){let s=t.reactionType>=3000;return{id:t.guid,conversationId:e,kind:t.kind,text:t.kind==="event"&&t.groupTitle?`Named the conversation \u201C${t.groupTitle}\u201D`:t.text,fromMe:t.isFromMe,sentAt:t.sentAt,sender:t.isFromMe?null:this.person(t.sender),targetId:t.targetGuid,reaction:t.kind==="reaction"?as[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((r)=>({id:r.guid,name:r.name,mimeType:r.mimeType,bytes:r.bytes,path:r.path&&ye(r.path)?r.path:null,url:null})),cursor:String(t.rowid)}}async archive(e){let t=this.open((n)=>this.find(n,e)),s=t?Ue(t):null;if(!t||!s)return{mode:"unsupported",removed:!1,detail:"Messages cannot open this conversation by address, so delete it there yourself."};if(t.messageCount===0)return{mode:"confirm",removed:!0,detail:"Already gone from Messages."};let r=this.rowNames(t);if(this.controller&&r.length>0){let n=await this.controller.remove(r,s,this.confirmDeletes);return{mode:"confirm",removed:!1,detail:n.ok?this.confirmDeletes?"Deleted in Messages.":"Messages is asking you to confirm the delete.":n.detail}}return this.openUrl(s),{mode:"confirm",removed:!1,detail:"Messages is open on the conversation: choose Conversation > Delete Conversation to finish."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>this.find(r,e));if(!t||t.messageCount>0)return;let s=this.rowNames(t);if(s.length>0)await this.controller.recover(s)}async attachment(e){if(!e.path||!ye(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 ls="im,mpim,public_channel,private_channel",ft={"+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"},Q=(e)=>Math.round(Number(e)*1000);function cs(e){let t=Object.entries(ft).find(([,r])=>r===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 ee extends Error{method;code;constructor(e,t){super(`Slack ${e} failed: ${t}`);this.method=e;this.code=t;this.name="SlackApiError"}}class te{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()??"",r=t.files??[];if(!s&&r.length===0)throw Error("Nothing to send.");let n=t.replyToId?await this.threadOf(e,t.replyToId):null;if(r.length===0){let a=await this.api("chat.postMessage",{channel:e,text:s,...n?{thread_ts:n}:{}},!0);return{id:a.ts,sentAt:Q(a.ts)}}let i=[];for(let a of r){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}:{},...n?{thread_ts:n}:{}},!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,r=!1){let n=cs(s),i=t.split(":")[0];await this.api(r?"reactions.remove":"reactions.add",{channel:e,timestamp:i,name:n},!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,r=null,n=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 ee("apps.connections.open",l.error??`http_${a.status}`);let o=new t(l.url);r=o;let d=Promise.resolve();o.onmessage=(c)=>{n=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(r=null,!s)setTimeout(i,Math.min(60000,1000*2**Math.min(n++,6)))}}catch{if(!s)setTimeout(i,Math.min(60000,1000*2**Math.min(n++,6)))}};return i(),()=>{s=!0,r?.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 n=await this.toMessage(s.channel,s);t({type:"message",conversationId:s.channel,message:n});return}let r=s.channel??s.item?.channel??null;t({type:"changed",conversationId:r})}async api(e,t={},s=!1){for(let r=0;;r++){let n=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:n.toString()}):await this.fetch(`${this.apiBase}/${e}?${n}`,{headers:{authorization:`Bearer ${this.token}`}});if(i.status===429&&r<3){await Bun.sleep(Math.min(30,Number(i.headers.get("retry-after"))||1)*1000);continue}let a=await i.json();if(!a.ok)throw new ee(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(!pe(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:ls,exclude_archived:!0,limit:200,...t?{cursor:t}:{}});e.push(...s.channels),t=s.response_metadata?.next_cursor??""}while(t);return e}async conversations(){let e=await this.whoami(),t=await this.list(),s=[];for(let r=0;r<t.length;r+=this.concurrency){let n=t.slice(r,r+this.concurrency);s.push(...await Promise.all(n.map((i)=>this.describe(i,e))))}return s.sort((r,n)=>(n.lastMessageAt??0)-(r.lastMessageAt??0))}async describe(e,t){let[{channel:s},r]=await Promise.all([this.api("conversations.info",{channel:e.id}).catch(()=>({channel:e})),this.api("conversations.history",{channel:e.id,limit:1}).catch(()=>({messages:[]}))]),n={...e,...s};this.channels.set(n.id,n);let i=r.messages[0],a=[];if(n.is_im)a=[await this.person(n.user)];else if(n.is_mpim){let l=await this.api("conversations.members",{channel:n.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: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?Q(i.ts):null,preview:i?.text||(i?.files?.length?"Attachment":null),previewFromMe:i?.user===t.userId,unread:n.unread_count_display??0,cursor:i?.ts??"0",visible:n.is_im||n.is_mpim?n.is_open!==!1:!0,archive:this.archiveSupport(n),url:`slack://channel?team=${t.teamId}&id=${n.id}`}}async messages(e,t={}){let s=await this.whoami(),r=[],n="",i=t.limit??1000;do{let l=await this.api("conversations.history",{channel:e,limit:Math.min(200,i),...t.after?{oldest:t.after,inclusive:!1}:{},...n?{cursor:n}:{}});r.push(...l.messages),n=l.has_more?l.response_metadata?.next_cursor??"":""}while(n&&r.length<i);r.sort((l,o)=>Number(l.ts)-Number(o.ts));let a=[];for(let l of r.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:Q(l.ts),sender:d===s.userId?null:await this.person(d),targetId:l.ts,reaction:ft[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(),r=await this.person(t.user),n=!!t.subtype&&!["file_share","thread_broadcast","me_message"].includes(t.subtype);return{id:t.ts,conversationId:e,kind:n?"event":"message",text:t.text??null,fromMe:t.user===s.userId,sentAt:Q(t.ts),sender:t.user===s.userId?null:r??(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?Q(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 we,readFileSync as fs}from"fs";import{basename as bs,dirname as Ze,extname as ys,join as He}from"path";import vs from"process";function Fe(e,t){let s=0n,r=0n;for(let n=t;n<e.length&&n<t+10;n++){let i=e[n];if(s|=BigInt(i&127)<<r,(i&128)===0)return{value:s,next:n+1};r+=7n}throw Error("Truncated varint")}function us(e){let t=[],s=0;while(s<e.length){let r=Fe(e,s);s=r.next;let n=Number(r.value>>3n),i=Number(r.value&7n);if(n===0)throw Error("Field number 0");if(i===0){let a=Fe(e,s);t.push({field:n,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:n,wire:i,value:l}),s+=a}else if(i===2){let a=Fe(e,s),l=a.next,o=l+Number(a.value);if(o>e.length)throw Error("Truncated length-delimited field");t.push({field:n,wire:i,value:e.subarray(l,o)}),s=o}else throw Error(`Unsupported wire type ${i}`)}return t}function se(e){if(!e||e.length===0)return null;try{return us(e)}catch{return null}}function ds(e,t){let s=e?.find((r)=>r.field===t&&r.wire===2);return s?s.value:null}function Le(e,t){return(e??[]).filter((s)=>s.field===t&&s.wire===2).map((s)=>s.value)}function re(e,t){let s=ds(e,t);if(!s)return null;let r=new TextDecoder("utf-8",{fatal:!0});try{return r.decode(s)}catch{return null}}function $e(e,t){let s=e?.find((r)=>r.field===t&&r.wire!==2);return s?s.value:null}import{Database as ms}from"bun:sqlite";import{homedir as hs}from"os";import{join as We}from"path";var bt=We(hs(),"Library","Group Containers","group.net.whatsapp.WhatsApp.shared"),je=We(bt,"ChatStorage.sqlite"),yt=We(bt,"Message"),ne=0,ps=1,gs=4,m={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 ve 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 Be{path;db;constructor(e=je){this.path=e;try{this.db=new ms(e,{readonly:!0}),this.db.query("SELECT 1 FROM ZWACHATSESSION LIMIT 1").all()}catch(t){throw new ve(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(`
61
61
  SELECT s.Z_PK AS pk, s.ZCONTACTJID AS jid, s.ZSESSIONTYPE AS type, s.ZPARTNERNAME AS name,
62
62
  s.ZARCHIVED AS archived, s.ZUNREADCOUNT AS unread, s.ZLASTMESSAGEDATE AS lastDate,
63
63
  s.ZLASTMESSAGETEXT AS lastText, COALESCE(last.ZISFROMME, 0) AS lastFromMe,
@@ -65,7 +65,7 @@ end run`;async function ss(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...
65
65
  (SELECT COUNT(*) FROM ZWAMESSAGE m WHERE m.ZCHATSESSION = s.Z_PK) AS messageCount
66
66
  FROM ZWACHATSESSION s
67
67
  LEFT JOIN ZWAMESSAGE last ON last.Z_PK = s.ZLASTMESSAGE
68
- WHERE s.ZSESSIONTYPE IN (${ne}, ${ds}, ${ms})
68
+ WHERE s.ZSESSIONTYPE IN (${ne}, ${ps}, ${gs})
69
69
  AND COALESCE(s.ZHIDDEN, 0) = 0 AND COALESCE(s.ZREMOVED, 0) = 0
70
70
  AND s.ZCONTACTJID IS NOT NULL ${e?"AND s.ZCONTACTJID = ?":""}
71
71
  ORDER BY s.ZLASTMESSAGEDATE DESC
@@ -90,4 +90,4 @@ end run`;async function ss(e,t){let s=Bun.spawn(["/usr/bin/osascript","-e",e,...
90
90
  WHERE m.ZCHATSESSION = ? AND m.Z_PK > ?
91
91
  ORDER BY m.Z_PK ${s}
92
92
  ${r}
93
- `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:U(i.date)??0,senderJid:i.senderJid,senderName:f(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var ys="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",bs={[m.Image]:"image/jpeg",[m.Video]:"video/mp4",[m.Audio]:"audio/ogg",[m.Gif]:"video/mp4",[m.Sticker]:"image/webp"},vs={[m.Image]:"Photo",[m.Video]:"Video",[m.Audio]:"Voice message",[m.Document]:"Document",[m.Gif]:"GIF",[m.Sticker]:"Sticker"},ws={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp",".heic":"image/heic",".mp4":"video/mp4",".mov":"video/quicktime",".opus":"audio/ogg",".ogg":"audio/ogg",".m4a":"audio/mp4",".mp3":"audio/mpeg",".pdf":"application/pdf"};function Ss(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function Ze(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function As(e){let t=e?.match(/^FN:(.*)$/m);return f(t?.[1])}class ie{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??Le,this.mediaRoot=e.mediaRoot??(e.databasePath?Be(je(e.databasePath),"Message"):gt),this.controller=e.controller,this.openUrl=e.openUrl??Ss,this.platform=e.platform??fs.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=Be(je(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>be(s))??null}pictures(e){let t=e.profilePicturePaths(),s=new Map;return(r)=>{if(!s.has(r))s.set(r,this.pictureFile(t.get(r))?r:null);return s.get(r)}}async avatar(e){let t=this.open((s)=>this.pictureFile(s.profilePicturePaths().get(e)));return G(t?new Uint8Array(hs(t)):null)}open(e){let t=new We(this.databasePath);try{return e(t)}finally{t.close()}}async status(){if(this.platform!=="darwin")return{connected:!1,detail:"WhatsApp is read from WhatsApp for Mac, so it needs a Mac.",needs:"unsupported-platform"};try{return this.open(()=>{return}),{connected:!0,detail:"Reading WhatsApp on this Mac."}}catch(e){if(e instanceof ye&&e.needsFullDiskAccess&&be(je(this.databasePath)))return{connected:!1,detail:"Grant Full Disk Access in System Settings > Privacy & Security.",needs:"full-disk-access"};return{connected:!1,detail:"Install WhatsApp for Mac and link it to your phone.",needs:"signed-out"}}}archiveSupport(e){if(this.controller)return{mode:"native",detail:ys};return this.url(e)?{mode:"confirm",detail:"Opens the chat in WhatsApp for you to archive it there."}:{mode:"unsupported",detail:"WhatsApp cannot open a group by link, so archive it there yourself."}}url(e){let[t,s]=e.jid.split("@");return s==="s.whatsapp.net"&&t&&/^\d{7,15}$/.test(t)?`whatsapp://send?phone=${t}`:null}async conversations(){return this.open((e)=>{let t=this.pictures(e);return e.sessions().map((s)=>{let r=s.type===ne,n=r?[{id:s.jid,name:s.name,avatar:t(s.jid)}]:e.members(s.pk).map((i)=>({id:i.jid,name:i.name,avatar:t(i.jid)}));return{provider:"whatsapp",id:s.jid,kind:r?"direct":"group",title:r?null:s.name,participants:n,workspace:null,lastMessageAt:s.lastMessageAt,preview:s.lastMessageText,previewFromMe:s.lastMessageFromMe,unread:s.unread,cursor:String(s.lastMessagePk),visible:!s.archived,archive:this.archiveSupport(s),url:this.url(s),avatar:r?null:t(s.jid)}})})}async messages(e,t={}){return this.open((s)=>{let r=s.session(e);if(!r)return[];let n=t.after||!t.limit?s.messages(r.pk,{afterPk:Number(t.after)||0,limit:t.limit}):s.messages(r.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(s),a=new Map(r.type===ne?[]:s.members(r.pk).map((l)=>[l.jid,l.name]));return n.flatMap((l)=>[this.toMessage(r,l,i),...this.reactions(r,l,a,i)])})}toMessage(e,t,s){let r=t.isFromMe?null:e.type===ne?{id:e.jid,name:e.name,avatar:s(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:s(t.senderJid)}:null,{kind:n,text:i,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:n,text:i,fromMe:t.isFromMe,sentAt:t.sentAt,sender:r,targetId:null,reaction:null,reactionRemoved:!1,replyToId:re(se(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,s,r){let n=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Ue(se(t.receipts),7))Ue(se(a),1).forEach((l,o)=>{let d=se(l),c=re(d,3);if(!c)return;let h=re(d,2),p=!h&&$e(d,6)===1n,y=h??(p?null:e.jid),u=Number($e(d,4)??0n);i.push({id:re(d,1)??`${n}:reaction:${o}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:p,sentAt:u>0?u:t.sentAt,sender:y?{id:y,name:s.get(y)??(y===e.jid?e.name:null),avatar:r(y)}:null,targetId:n,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case m.Text:case m.Link:return{kind:"message",text:f(e.text),unsent:!1};case m.Image:case m.Video:case m.Gif:return{kind:"message",text:f(e.text)??f(t?.title),unsent:!1};case m.Contact:{let s=As(t?.vcard??null)??f(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case m.Location:{let s=f(t?.title),r=t?.latitude!=null&&t.longitude!=null&&(t.latitude!==0||t.longitude!==0)?`https://maps.apple.com/?ll=${t.latitude},${t.longitude}`:null;return{kind:"message",text:[s??"Shared a location",r].filter(Boolean).join(" "),unsent:!1}}case m.GroupEvent:case m.Call:return{kind:"event",text:f(e.text),unsent:!1};case m.Deleted:return{kind:"message",text:null,unsent:!0};case m.Audio:case m.Document:case m.Sticker:return{kind:"message",text:null,unsent:!1};default:return f(e.text)||t?.path?{kind:"message",text:f(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=vs[e.type];if(!t||!t.path&&!s||e.type===m.Text||e.type===m.Link)return[];let r=t.path?Be(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?ps(t.path):null,a=e.type===m.Document?f(t.title)??f(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?ws[gs(t.path).toLowerCase()]:void 0)??bs[e.type]??null,bytes:t.bytes,path:r&&be(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,Ze(e.jid)].filter((t)=>!!t))]}async settled(e,t){let s=Date.now()+this.settleMs;while(!0){if(this.open((r)=>r.session(e))?.archived===t)return!0;if(Date.now()>=s)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((r)=>r.session(e));if(!t)return{mode:"unsupported",removed:!1,detail:"WhatsApp no longer has this chat."};if(t.archived)return{mode:"native",removed:!0,detail:"Already archived in WhatsApp."};if(this.controller){let r=await this.controller.archive(this.rowNames(t));if(!r.ok)return{mode:"native",removed:!1,detail:r.detail};let n=await this.settled(e,!0);return{mode:"native",removed:n,detail:n?"Archived in WhatsApp.":"WhatsApp did not record the archive."}}let s=this.url(t);if(s)this.openUrl(s);return{mode:"confirm",removed:!1,detail:s?"WhatsApp is open on the chat: choose Archive chat from its menu to finish.":"Archive it in WhatsApp: WhatsApp cannot open a group by link."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>r.session(e));if(!t?.archived)return;if((await this.controller.unarchive(this.rowNames(t))).ok)await this.settled(e,!1)}async attachment(e){if(!e.path||!be(e.path))return new Response("Not found",{status:404});return new Response(Bun.file(e.path),{headers:{"content-type":e.mimeType??"application/octet-stream"}})}}function xs(e,t){switch(e){case"imessage":return new X(t);case"slack":return new te(t);case"discord":return new V(t);case"whatsapp":return new ie(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}import{createMessenger as Qr,messagingChannel as en,MessagingError as tn}from"@stacksjs/orm";async function Hr(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return le(e);case"discord":return oe(e);case"teams":return ue(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Gr(e,t,s){return ce(e,t,s)}async function Jr(e,t,s){return W(e,t,s)}async function zr(e,t){return de(e,t)}function Vr(e){j(e)}function Kr(e){F(e)}function qr(e){Z(e)}export{tn as MessagingError,Kr as configureDiscord,Vr as configureSlack,qr as configureTeams,Qr as createMessenger,we as discord,ft as inbox,en as messagingChannel,Hr as send,Jr as sendToDiscord,Gr as sendToSlack,zr as sendToTeams,Ae as slack,_e as teams};
93
+ `).all(e,t.afterPk??0).map((i)=>({pk:i.pk,stanzaId:i.stanzaId,type:i.type,text:i.text,isFromMe:!!i.isFromMe,sentAt:U(i.date)??0,senderJid:i.senderJid,senderName:f(i.senderName),media:i.mediaPk==null?null:{path:i.mediaPath,vcard:i.vcard,title:i.title,bytes:i.bytes??0,latitude:i.latitude,longitude:i.longitude},metadata:i.metadata,receipts:i.receipts}))}}var ws="Archives the chat in WhatsApp. WhatsApp brings it back when a new message arrives, unless Keep chats archived is on.",Ss={[m.Image]:"image/jpeg",[m.Video]:"video/mp4",[m.Audio]:"audio/ogg",[m.Gif]:"video/mp4",[m.Sticker]:"image/webp"},As={[m.Image]:"Photo",[m.Video]:"Video",[m.Audio]:"Voice message",[m.Document]:"Document",[m.Gif]:"GIF",[m.Sticker]:"Sticker"},xs={".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 ks(e){Bun.spawn(["/usr/bin/open",e],{stdio:["ignore","ignore","ignore"]})}function Ge(e){let[t=e,s]=e.split("@");return s==="s.whatsapp.net"&&/^\d{7,15}$/.test(t)?`+${t}`:t}function Ms(e){let t=e?.match(/^FN:(.*)$/m);return f(t?.[1])}class ie{provider="whatsapp";label="WhatsApp";databasePath;mediaRoot;controller;openUrl;platform;settleMs;constructor(e={}){this.databasePath=e.databasePath??je,this.mediaRoot=e.mediaRoot??(e.databasePath?He(Ze(e.databasePath),"Message"):yt),this.controller=e.controller,this.openUrl=e.openUrl??ks,this.platform=e.platform??vs.platform,this.settleMs=e.settleMs??6000}pictureFile(e){if(!e||e.includes(".."))return null;let t=He(Ze(this.databasePath),e);return[`${t}.jpg`,`${t}.thumb`,t].find((s)=>we(s))??null}pictures(e){let t=e.profilePicturePaths(),s=new Map;return(r)=>{if(!s.has(r))s.set(r,this.pictureFile(t.get(r))?r:null);return s.get(r)}}async avatar(e){let t=this.open((s)=>this.pictureFile(s.profilePicturePaths().get(e)));return G(t?new Uint8Array(fs(t)):null)}open(e){let t=new Be(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 ve&&e.needsFullDiskAccess&&we(Ze(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:ws};return this.url(e)?{mode:"confirm",detail:"Opens the chat in WhatsApp for you to archive it there."}:{mode:"unsupported",detail:"WhatsApp cannot open a group by link, so archive it there yourself."}}url(e){let[t,s]=e.jid.split("@");return s==="s.whatsapp.net"&&t&&/^\d{7,15}$/.test(t)?`whatsapp://send?phone=${t}`:null}async conversations(){return this.open((e)=>{let t=this.pictures(e);return e.sessions().map((s)=>{let r=s.type===ne,n=r?[{id:s.jid,name:s.name,avatar:t(s.jid)}]:e.members(s.pk).map((i)=>({id:i.jid,name:i.name,avatar:t(i.jid)}));return{provider:"whatsapp",id:s.jid,kind:r?"direct":"group",title:r?null:s.name,participants:n,workspace:null,lastMessageAt:s.lastMessageAt,preview:s.lastMessageText,previewFromMe:s.lastMessageFromMe,unread:s.unread,cursor:String(s.lastMessagePk),visible:!s.archived,archive:this.archiveSupport(s),url:this.url(s),avatar:r?null:t(s.jid)}})})}async messages(e,t={}){return this.open((s)=>{let r=s.session(e);if(!r)return[];let n=t.after||!t.limit?s.messages(r.pk,{afterPk:Number(t.after)||0,limit:t.limit}):s.messages(r.pk,{newestFirst:!0,limit:t.limit}).reverse(),i=this.pictures(s),a=new Map(r.type===ne?[]:s.members(r.pk).map((l)=>[l.jid,l.name]));return n.flatMap((l)=>[this.toMessage(r,l,i),...this.reactions(r,l,a,i)])})}toMessage(e,t,s){let r=t.isFromMe?null:e.type===ne?{id:e.jid,name:e.name,avatar:s(e.jid)}:t.senderJid?{id:t.senderJid,name:t.senderName,avatar:s(t.senderJid)}:null,{kind:n,text:i,unsent:a}=this.describe(t);return{id:t.stanzaId??`wa-${t.pk}`,conversationId:e.jid,kind:n,text:i,fromMe:t.isFromMe,sentAt:t.sentAt,sender:r,targetId:null,reaction:null,reactionRemoved:!1,replyToId:re(se(t.metadata),5),editedAt:null,unsent:a,attachments:this.attachments(t),cursor:String(t.pk)}}reactions(e,t,s,r){let n=t.stanzaId??`wa-${t.pk}`,i=[];for(let a of Le(se(t.receipts),7))Le(se(a),1).forEach((l,o)=>{let d=se(l),c=re(d,3);if(!c)return;let h=re(d,2),p=!h&&$e(d,6)===1n,b=h??(p?null:e.jid),u=Number($e(d,4)??0n);i.push({id:re(d,1)??`${n}:reaction:${o}`,conversationId:e.jid,kind:"reaction",text:null,fromMe:p,sentAt:u>0?u:t.sentAt,sender:b?{id:b,name:s.get(b)??(b===e.jid?e.name:null),avatar:r(b)}:null,targetId:n,reaction:c,reactionRemoved:!1,replyToId:null,editedAt:null,unsent:!1,attachments:[],cursor:String(t.pk)})});return i}describe(e){let t=e.media;switch(e.type){case m.Text:case m.Link:return{kind:"message",text:f(e.text),unsent:!1};case m.Image:case m.Video:case m.Gif:return{kind:"message",text:f(e.text)??f(t?.title),unsent:!1};case m.Contact:{let s=Ms(t?.vcard??null)??f(t?.title);return{kind:"message",text:s?`Contact card: ${s}`:"Contact card",unsent:!1}}case m.Location:{let s=f(t?.title),r=t?.latitude!=null&&t.longitude!=null&&(t.latitude!==0||t.longitude!==0)?`https://maps.apple.com/?ll=${t.latitude},${t.longitude}`:null;return{kind:"message",text:[s??"Shared a location",r].filter(Boolean).join(" "),unsent:!1}}case m.GroupEvent:case m.Call:return{kind:"event",text:f(e.text),unsent:!1};case m.Deleted:return{kind:"message",text:null,unsent:!0};case m.Audio:case m.Document:case m.Sticker:return{kind:"message",text:null,unsent:!1};default:return f(e.text)||t?.path?{kind:"message",text:f(e.text),unsent:!1}:{kind:"event",text:null,unsent:!1}}}attachments(e){let t=e.media,s=As[e.type];if(!t||!t.path&&!s||e.type===m.Text||e.type===m.Link)return[];let r=t.path?He(this.mediaRoot,t.path):null,n=t.vcard&&/^[\w.+-]+\/[\w.+-]+/.test(t.vcard)?t.vcard.split(";")[0].trim():null,i=t.path?bs(t.path):null,a=e.type===m.Document?f(t.title)??f(e.text)??i??s:i??s;return[{id:`${e.stanzaId??`wa-${e.pk}`}:media`,name:a,mimeType:n??(t.path?xs[ys(t.path).toLowerCase()]:void 0)??Ss[e.type]??null,bytes:t.bytes,path:r&&we(r)?r:null,url:null}]}rowNames(e){return[...new Set([e.name,Ge(e.jid)].filter((t)=>!!t))]}async settled(e,t){let s=Date.now()+this.settleMs;while(!0){if(this.open((r)=>r.session(e))?.archived===t)return!0;if(Date.now()>=s)return!1;await Bun.sleep(250)}}async archive(e){let t=this.open((r)=>r.session(e));if(!t)return{mode:"unsupported",removed:!1,detail:"WhatsApp no longer has this chat."};if(t.archived)return{mode:"native",removed:!0,detail:"Already archived in WhatsApp."};if(this.controller){let r=await this.controller.archive(this.rowNames(t));if(!r.ok)return{mode:"native",removed:!1,detail:r.detail};let n=await this.settled(e,!0);return{mode:"native",removed:n,detail:n?"Archived in WhatsApp.":"WhatsApp did not record the archive."}}let s=this.url(t);if(s)this.openUrl(s);return{mode:"confirm",removed:!1,detail:s?"WhatsApp is open on the chat: choose Archive chat from its menu to finish.":"Archive it in WhatsApp: WhatsApp cannot open a group by link."}}async unarchive(e){if(!this.controller)return;let t=this.open((r)=>r.session(e));if(!t?.archived)return;if((await this.controller.unarchive(this.rowNames(t))).ok)await this.settled(e,!1)}async attachment(e){if(!e.path||!we(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 _s(e,t){switch(e){case"imessage":return new X(t);case"slack":return new te(t);case"discord":return new V(t);case"whatsapp":return new ie(t);default:throw Error(`Unknown inbox driver: ${String(e)}`)}}import{createMessenger as sn,messagingChannel as rn,MessagingError as nn}from"@stacksjs/orm";async function zr(e,t={}){let s=t.driver??"slack";switch(s){case"slack":return ue(e);case"discord":return le(e);case"teams":return me(e);default:return{success:!1,provider:s,message:`Unknown chat driver: ${s}`}}}async function Vr(e,t,s){return de(e,t,s)}async function Kr(e,t,s){return W(e,t,s)}async function qr(e,t){return he(e,t)}function Yr(e){j(e)}function Xr(e){F(e)}function Qr(e){Z(e)}export{nn as MessagingError,Xr as configureDiscord,Yr as configureSlack,Qr as configureTeams,sn as createMessenger,Ae as discord,vt as inbox,rn as messagingChannel,zr as send,Kr as sendToDiscord,Vr as sendToSlack,qr as sendToTeams,ke as slack,Te 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.422",
5
+ "version": "0.75.423",
6
6
  "description": "Easily interact with chat APIs.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,14 +58,14 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "dependencies": {
61
- "@stacksjs/orm": "0.75.422",
62
- "@stacksjs/config": "0.75.422",
63
- "@stacksjs/logging": "0.75.422"
61
+ "@stacksjs/orm": "0.75.423",
62
+ "@stacksjs/config": "0.75.423",
63
+ "@stacksjs/logging": "0.75.423"
64
64
  },
65
65
  "devDependencies": {
66
- "@stacksjs/cli": "0.75.422",
67
- "@stacksjs/error-handling": "0.75.422",
68
- "@stacksjs/types": "0.75.422",
66
+ "@stacksjs/cli": "0.75.423",
67
+ "@stacksjs/error-handling": "0.75.423",
68
+ "@stacksjs/types": "0.75.423",
69
69
  "better-dx": "^0.2.24"
70
70
  }
71
71
  }