@stacksjs/chat 0.75.226 → 0.75.233

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.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +7 -7
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 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});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};var Ae={};ae(Ae,{Driver:()=>B,SlackDriver:()=>B,configure:()=>j,default:()=>Mt,driver:()=>kt,resolveConfig:()=>Ve,send:()=>le,sendWebhook:()=>ce});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};var _e={};ae(_e,{Driver:()=>H,TeamsDriver:()=>H,assertWebhookUrl:()=>N,configure:()=>Z,default:()=>Tt,driver:()=>Et,resolveConfig:()=>Ke,send:()=>ue,sendCard:()=>qe,sendWebhook:()=>de});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 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});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(`
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,
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.226",
5
+ "version": "0.75.233",
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.226",
62
- "@stacksjs/config": "0.75.226",
63
- "@stacksjs/logging": "0.75.226"
61
+ "@stacksjs/orm": "0.75.233",
62
+ "@stacksjs/config": "0.75.233",
63
+ "@stacksjs/logging": "0.75.233"
64
64
  },
65
65
  "devDependencies": {
66
- "@stacksjs/cli": "0.75.226",
67
- "@stacksjs/error-handling": "0.75.226",
68
- "@stacksjs/types": "0.75.226",
66
+ "@stacksjs/cli": "0.75.233",
67
+ "@stacksjs/error-handling": "0.75.233",
68
+ "@stacksjs/types": "0.75.233",
69
69
  "better-dx": "^0.2.24"
70
70
  }
71
71
  }