spectrum-ts 0.9.0 → 0.9.1

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.
@@ -2,10 +2,38 @@ import {
2
2
  bufferToStream,
3
3
  readSchema,
4
4
  streamSchema
5
- } from "./chunk-CZIWNTXP.js";
5
+ } from "./chunk-OIXH5S65.js";
6
+ import {
7
+ resolveContents
8
+ } from "./chunk-U6WCQVVX.js";
6
9
 
7
- // src/content/richlink.ts
10
+ // src/content/group.ts
8
11
  import z from "zod";
12
+ var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
13
+ var groupSchema = z.object({
14
+ type: z.literal("group"),
15
+ items: z.array(z.custom(isMessage)).min(2)
16
+ });
17
+ var asGroup = (input) => groupSchema.parse({ type: "group", items: input.items });
18
+ var stubOutboundMessage = (content) => ({ id: "", content });
19
+ function group(...items) {
20
+ return {
21
+ build: async () => {
22
+ const resolved = await resolveContents(items);
23
+ const members = [];
24
+ for (const item of resolved) {
25
+ if (item.type === "group" || item.type === "reaction") {
26
+ throw new Error(`group() cannot contain "${item.type}" items`);
27
+ }
28
+ members.push(stubOutboundMessage(item));
29
+ }
30
+ return asGroup({ items: members });
31
+ }
32
+ };
33
+ }
34
+
35
+ // src/content/richlink.ts
36
+ import z2 from "zod";
9
37
 
10
38
  // src/utils/link-metadata.ts
11
39
  import ogs from "open-graph-scraper";
@@ -76,22 +104,22 @@ var fetchImage = async (url) => {
76
104
  };
77
105
 
78
106
  // src/content/richlink.ts
79
- var richlinkCoverSchema = z.object({
80
- mimeType: z.string().min(1).optional(),
107
+ var richlinkCoverSchema = z2.object({
108
+ mimeType: z2.string().min(1).optional(),
81
109
  read: readSchema,
82
110
  stream: streamSchema
83
111
  });
84
- var optionalStringAccessor = z.function({
112
+ var optionalStringAccessor = z2.function({
85
113
  input: [],
86
- output: z.promise(z.string().min(1).optional())
114
+ output: z2.promise(z2.string().min(1).optional())
87
115
  });
88
- var coverAccessor = z.function({
116
+ var coverAccessor = z2.function({
89
117
  input: [],
90
- output: z.promise(richlinkCoverSchema.optional())
118
+ output: z2.promise(richlinkCoverSchema.optional())
91
119
  });
92
- var richlinkSchema = z.object({
93
- type: z.literal("richlink"),
94
- url: z.url(),
120
+ var richlinkSchema = z2.object({
121
+ type: z2.literal("richlink"),
122
+ url: z2.url(),
95
123
  title: optionalStringAccessor,
96
124
  summary: optionalStringAccessor,
97
125
  cover: coverAccessor
@@ -136,6 +164,8 @@ function richlink(url) {
136
164
  }
137
165
 
138
166
  export {
167
+ asGroup,
168
+ group,
139
169
  asRichlink,
140
170
  richlink
141
171
  };
@@ -555,15 +555,17 @@ function custom(raw) {
555
555
 
556
556
  // src/content/reaction.ts
557
557
  import z5 from "zod";
558
+ var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
558
559
  var reactionSchema = z5.object({
559
560
  type: z5.literal("reaction"),
560
561
  emoji: z5.string().min(1),
561
- target: z5.string().min(1)
562
+ target: z5.custom(isMessage, {
563
+ message: "reaction target must be a Message"
564
+ })
562
565
  });
563
566
  var asReaction = (input) => reactionSchema.parse({ type: "reaction", ...input });
564
567
  function reaction(emoji, target) {
565
- const targetId = typeof target === "string" ? target : target.id;
566
- return { build: async () => asReaction({ emoji, target: targetId }) };
568
+ return { build: async () => asReaction({ emoji, target }) };
567
569
  }
568
570
 
569
571
  // src/utils/stream.ts
@@ -88,6 +88,63 @@ var warnUnsupported = (err, fallbackPlatform) => {
88
88
  supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
89
89
  );
90
90
  };
91
+ var providerMessageCoreKeys = /* @__PURE__ */ new Set([
92
+ "content",
93
+ "id",
94
+ "sender",
95
+ "space",
96
+ "timestamp"
97
+ ]);
98
+ var extractExtras = (raw, definition) => {
99
+ const entries = Object.entries(raw).filter(
100
+ ([key]) => !providerMessageCoreKeys.has(key)
101
+ );
102
+ const extra = Object.fromEntries(entries);
103
+ return definition.message?.schema ? definition.message.schema.parse(extra) : extra;
104
+ };
105
+ function wrapProviderMessage(raw, ctx) {
106
+ const wrappedContent = wrapNestedContent(raw.content, ctx);
107
+ return buildMessage({
108
+ id: raw.id,
109
+ content: wrappedContent,
110
+ sender: raw.sender,
111
+ timestamp: raw.timestamp ?? /* @__PURE__ */ new Date(),
112
+ extras: extractExtras(raw, ctx.definition),
113
+ spaceRef: ctx.spaceRef,
114
+ space: ctx.space,
115
+ definition: ctx.definition,
116
+ client: ctx.client,
117
+ config: ctx.config,
118
+ direction: "inbound"
119
+ });
120
+ }
121
+ var wrapNestedContent = (content, ctx) => {
122
+ if (content.type === "reaction") {
123
+ const target = content.target;
124
+ if (isRawProviderRecord(target)) {
125
+ return {
126
+ ...content,
127
+ target: wrapProviderMessage(target, ctx)
128
+ };
129
+ }
130
+ return content;
131
+ }
132
+ if (content.type === "group") {
133
+ const items = content.items.map((item) => {
134
+ const raw = item;
135
+ return isRawProviderRecord(raw) ? wrapProviderMessage(raw, ctx) : item;
136
+ });
137
+ return { ...content, items };
138
+ }
139
+ return content;
140
+ };
141
+ var isRawProviderRecord = (v) => {
142
+ if (typeof v !== "object" || v === null) {
143
+ return false;
144
+ }
145
+ const record = v;
146
+ return "id" in record && "content" in record && typeof record.react !== "function" && typeof record.reply !== "function";
147
+ };
91
148
  function buildSpace(params) {
92
149
  const { spaceRef, extras, typingCtx, definition, client, config } = params;
93
150
  let space;
@@ -98,7 +155,7 @@ function buildSpace(params) {
98
155
  }
99
156
  await definition.actions.reactToMessage({
100
157
  space: spaceRef,
101
- messageId: item.target,
158
+ target: item.target,
102
159
  reaction: item.emoji,
103
160
  client,
104
161
  config
@@ -130,9 +187,31 @@ function buildSpace(params) {
130
187
  `Platform "${definition.name}" send did not return a message id`
131
188
  );
132
189
  }
190
+ const outboundContent = item.type === "group" && sendResult.groupMembers ? {
191
+ ...item,
192
+ items: item.items.map((stub, idx) => {
193
+ const member = sendResult?.groupMembers?.[idx];
194
+ if (!member?.id) {
195
+ return stub;
196
+ }
197
+ return buildMessage({
198
+ id: member.id,
199
+ content: stub.content,
200
+ sender: member.sender,
201
+ timestamp: member.timestamp ?? /* @__PURE__ */ new Date(),
202
+ extras: {},
203
+ spaceRef,
204
+ space,
205
+ definition,
206
+ client,
207
+ config,
208
+ direction: "outbound"
209
+ });
210
+ })
211
+ } : item;
133
212
  return buildMessage({
134
213
  id: sendResult.id,
135
- content: item,
214
+ content: outboundContent,
136
215
  sender: sendResult.sender,
137
216
  timestamp: sendResult.timestamp ?? /* @__PURE__ */ new Date(),
138
217
  extras: {},
@@ -162,6 +241,40 @@ function buildSpace(params) {
162
241
  }
163
242
  return results;
164
243
  }
244
+ async function getMessageImpl(id) {
245
+ if (!definition.actions.getMessage) {
246
+ warnUnsupported(
247
+ UnsupportedError.action("getMessage", definition.name),
248
+ definition.name
249
+ );
250
+ return;
251
+ }
252
+ let raw;
253
+ try {
254
+ raw = await definition.actions.getMessage({
255
+ space: spaceRef,
256
+ messageId: id,
257
+ client,
258
+ config
259
+ });
260
+ } catch (err) {
261
+ if (err instanceof UnsupportedError) {
262
+ warnUnsupported(err, definition.name);
263
+ return;
264
+ }
265
+ throw err;
266
+ }
267
+ if (!raw) {
268
+ return;
269
+ }
270
+ return wrapProviderMessage(raw, {
271
+ client,
272
+ config,
273
+ definition,
274
+ space,
275
+ spaceRef
276
+ });
277
+ }
165
278
  space = {
166
279
  ...extras,
167
280
  ...spaceRef,
@@ -169,6 +282,7 @@ function buildSpace(params) {
169
282
  edit: async (message, newContent) => {
170
283
  await message.edit(newContent);
171
284
  },
285
+ getMessage: getMessageImpl,
172
286
  startTyping: async () => {
173
287
  await definition.actions.startTyping?.(typingCtx);
174
288
  },
@@ -189,6 +303,7 @@ function buildSpace(params) {
189
303
  }
190
304
  function buildMessage(params) {
191
305
  const { definition, client, config, spaceRef, space } = params;
306
+ let self;
192
307
  const react = async (reaction) => {
193
308
  if (!definition.actions.reactToMessage) {
194
309
  warnUnsupported(
@@ -197,10 +312,15 @@ function buildMessage(params) {
197
312
  );
198
313
  return;
199
314
  }
315
+ if (!self) {
316
+ throw new Error(
317
+ "react() called before message construction completed (internal bug)"
318
+ );
319
+ }
200
320
  try {
201
321
  await definition.actions.reactToMessage({
202
322
  space: spaceRef,
203
- messageId: params.id,
323
+ target: self,
204
324
  reaction,
205
325
  client,
206
326
  config
@@ -268,7 +388,7 @@ function buildMessage(params) {
268
388
  }
269
389
  const senderWithPlatform = params.sender === void 0 ? void 0 : { ...params.sender, __platform: definition.name };
270
390
  if (params.direction === "outbound") {
271
- return {
391
+ const outbound = {
272
392
  ...params.extras,
273
393
  id: params.id,
274
394
  content: params.content,
@@ -308,8 +428,10 @@ function buildMessage(params) {
308
428
  space,
309
429
  timestamp: params.timestamp
310
430
  };
431
+ self = outbound;
432
+ return outbound;
311
433
  }
312
- return {
434
+ const inbound = {
313
435
  ...params.extras,
314
436
  id: params.id,
315
437
  content: params.content,
@@ -321,6 +443,8 @@ function buildMessage(params) {
321
443
  space,
322
444
  timestamp: params.timestamp
323
445
  };
446
+ self = inbound;
447
+ return inbound;
324
448
  }
325
449
 
326
450
  // src/platform/define.ts
@@ -507,7 +631,7 @@ export {
507
631
  text,
508
632
  resolveContents,
509
633
  UnsupportedError,
634
+ wrapProviderMessage,
510
635
  buildSpace,
511
- buildMessage,
512
636
  definePlatform
513
637
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-B8g0pvfg.js';
2
- export { A as AnyPlatformDef, E as EventProducer, h as PlatformInstance, i as PlatformMessage, j as PlatformSpace, k as PlatformUser, l as SchemaMessage } from './types-B8g0pvfg.js';
1
+ import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-D5KhSXLy.js';
2
+ export { A as AnyPlatformDef, E as EventProducer, h as PlatformInstance, i as PlatformMessage, j as PlatformSpace, k as PlatformUser, l as SchemaMessage } from './types-D5KhSXLy.js';
3
3
  import vCard from 'vcf';
4
4
  import z__default from 'zod';
5
5
  export { M as ManagedStream, m as mergeStreams, s as stream } from './stream-B55k7W8-.js';
@@ -122,21 +122,39 @@ declare function contact(input: string | ContactInput | vCard): ContentBuilder;
122
122
 
123
123
  declare function custom(raw: unknown): ContentBuilder;
124
124
 
125
+ /**
126
+ * A `group` bundles multiple messages into one logical unit (e.g. an album
127
+ * of images sent together). Each item is a full `Message` — addressable by
128
+ * id, reactable via `.react()`, replyable via `.reply()`.
129
+ *
130
+ * Groups do not nest, and reactions cannot be group members. Enforced by the
131
+ * `group()` builder; platforms may additionally reject unsupported item
132
+ * content types at send time.
133
+ */
134
+ declare const groupSchema: z__default.ZodObject<{
135
+ type: z__default.ZodLiteral<"group">;
136
+ items: z__default.ZodArray<z__default.ZodCustom<Message, Message>>;
137
+ }, z__default.core.$strip>;
138
+ type Group = z__default.infer<typeof groupSchema>;
139
+ declare function group(...items: [ContentInput, ContentInput, ...ContentInput[]]): ContentBuilder;
140
+
125
141
  declare const reactionSchema: z__default.ZodObject<{
126
142
  type: z__default.ZodLiteral<"reaction">;
127
143
  emoji: z__default.ZodString;
128
- target: z__default.ZodString;
144
+ target: z__default.ZodCustom<Message, Message>;
129
145
  }, z__default.core.$strip>;
130
146
  type Reaction = z__default.infer<typeof reactionSchema>;
131
147
  /**
132
- * Construct a `reaction` content value. Passing a `Message` extracts its id;
133
- * a string is treated as the target message id directly.
148
+ * Construct a `reaction` content value targeting the given message.
134
149
  *
135
150
  * `space.send(reaction(emoji, message))` is sugar for `message.react(emoji)`.
136
151
  * Reactions are fire-and-forget — the returned `OutboundMessage` will be
137
152
  * `undefined` because platforms do not surface a message id for reactions.
153
+ *
154
+ * To react to a message known only by id, resolve it first via
155
+ * `space.getMessage(id)`.
138
156
  */
139
- declare function reaction(emoji: string, target: Message | string): ContentBuilder;
157
+ declare function reaction(emoji: string, target: Message): ContentBuilder;
140
158
 
141
159
  declare const resolveContents: (items: readonly ContentInput[]) => Promise<Content[]>;
142
160
 
@@ -2122,14 +2140,32 @@ type SpectrumInstance<Providers extends PlatformProviderConfig[] = PlatformProvi
2122
2140
  edit(message: OutboundMessage, newContent: ContentInput): Promise<void>;
2123
2141
  responding<T>(space: Space, fn: () => T | Promise<T>): Promise<T>;
2124
2142
  };
2143
+ /**
2144
+ * Runtime behavior tweaks for a Spectrum instance.
2145
+ */
2146
+ interface SpectrumOptions {
2147
+ /**
2148
+ * When `true`, inbound `group` messages are never delivered whole. Instead,
2149
+ * each group item is yielded from `spectrum.messages` as its own
2150
+ * `[space, message]` tuple, in order. Items retain their individual
2151
+ * `id`, `sender`, `timestamp`, and `.react()` / `.reply()` methods.
2152
+ *
2153
+ * Does not affect outbound `group(...)` sends or `space.getMessage(id)`.
2154
+ *
2155
+ * @default false
2156
+ */
2157
+ flattenGroups?: boolean;
2158
+ }
2125
2159
  declare function Spectrum<const Providers extends PlatformProviderConfig[]>(options: {
2126
2160
  projectId: string;
2127
2161
  projectSecret: string;
2128
2162
  providers: [...Providers];
2163
+ options?: SpectrumOptions;
2129
2164
  } | {
2130
2165
  projectId?: never;
2131
2166
  projectSecret?: never;
2132
2167
  providers: [...Providers];
2168
+ options?: SpectrumOptions;
2133
2169
  }): Promise<SpectrumInstance<Providers>>;
2134
2170
 
2135
2171
  type SubscriptionStatus = "active" | "canceled" | "past_due";
@@ -2198,4 +2234,4 @@ declare class UnsupportedError extends Error {
2198
2234
  declare const fromVCard: (vcf: string) => ContactInput;
2199
2235
  declare const toVCard: (contact: Contact) => Promise<string>;
2200
2236
 
2201
- export { type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, Content, ContentBuilder, ContentInput, type DedicatedTokenData, Emoji, type EmojiKey, type ImessageInfoData, Message, Platform, PlatformDef, PlatformProviderConfig, type PlatformStatus, type PlatformsData, type Reaction, type Richlink, type SharedTokenData, Space, Spectrum, SpectrumCloudError, type SpectrumInstance, type SubscriptionData, type SubscriptionStatus, type TokenData, UnsupportedError, type UnsupportedKind, User, type Voice, attachment, cloud, contact, custom, definePlatform, fromVCard, reaction, resolveContents, richlink, text, toVCard, voice };
2237
+ export { type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, Content, ContentBuilder, ContentInput, type DedicatedTokenData, Emoji, type EmojiKey, type Group, type ImessageInfoData, Message, Platform, PlatformDef, PlatformProviderConfig, type PlatformStatus, type PlatformsData, type Reaction, type Richlink, type SharedTokenData, Space, Spectrum, SpectrumCloudError, type SpectrumInstance, type SubscriptionData, type SubscriptionStatus, type TokenData, UnsupportedError, type UnsupportedKind, User, type Voice, attachment, cloud, contact, custom, definePlatform, fromVCard, group, reaction, resolveContents, richlink, text, toVCard, voice };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
+ group,
2
3
  richlink
3
- } from "./chunk-6ZOLTQDN.js";
4
+ } from "./chunk-HU2EOF3K.js";
4
5
  import {
5
6
  SpectrumCloudError,
6
7
  attachment,
@@ -15,15 +16,15 @@ import {
15
16
  stream,
16
17
  streamSchema,
17
18
  toVCard
18
- } from "./chunk-CZIWNTXP.js";
19
+ } from "./chunk-OIXH5S65.js";
19
20
  import {
20
21
  UnsupportedError,
21
- buildMessage,
22
22
  buildSpace,
23
23
  definePlatform,
24
24
  resolveContents,
25
- text
26
- } from "./chunk-PLJI5FTO.js";
25
+ text,
26
+ wrapProviderMessage
27
+ } from "./chunk-U6WCQVVX.js";
27
28
 
28
29
  // src/content/voice.ts
29
30
  import { createReadStream } from "fs";
@@ -2063,28 +2064,32 @@ var Emoji = { ...GeneratedEmoji, ...aliases };
2063
2064
 
2064
2065
  // src/spectrum.ts
2065
2066
  import z2 from "zod";
2066
- var providerMessageCoreKeys = /* @__PURE__ */ new Set([
2067
- "content",
2068
- "id",
2069
- "sender",
2070
- "space",
2071
- "timestamp"
2072
- ]);
2067
+ var spectrumOptionsSchema = z2.object({
2068
+ flattenGroups: z2.boolean().optional()
2069
+ }).optional();
2073
2070
  var spectrumConfigSchema = z2.union([
2074
2071
  z2.object({
2075
2072
  projectId: z2.string().min(1),
2076
2073
  projectSecret: z2.string().min(1),
2077
- providers: z2.array(z2.custom())
2074
+ providers: z2.array(z2.custom()),
2075
+ options: spectrumOptionsSchema
2078
2076
  }),
2079
2077
  z2.object({
2080
2078
  projectId: z2.undefined().optional(),
2081
2079
  projectSecret: z2.undefined().optional(),
2082
- providers: z2.array(z2.custom())
2080
+ providers: z2.array(z2.custom()),
2081
+ options: spectrumOptionsSchema
2083
2082
  })
2084
2083
  ]);
2085
2084
  async function Spectrum(options) {
2086
2085
  spectrumConfigSchema.parse(options);
2087
- const { projectId, projectSecret, providers } = options;
2086
+ const {
2087
+ projectId,
2088
+ projectSecret,
2089
+ providers,
2090
+ options: runtimeOptions
2091
+ } = options;
2092
+ const flattenGroups = runtimeOptions?.flattenGroups ?? false;
2088
2093
  const platformStates = /* @__PURE__ */ new Map();
2089
2094
  const customEventStreams = /* @__PURE__ */ new Map();
2090
2095
  let stopped = false;
@@ -2132,11 +2137,6 @@ async function Spectrum(options) {
2132
2137
  });
2133
2138
  const bindSend = async function* () {
2134
2139
  for await (const msg of raw) {
2135
- const extraEntries = Object.entries(msg).filter(
2136
- ([key]) => !providerMessageCoreKeys.has(key)
2137
- );
2138
- const extra = Object.fromEntries(extraEntries);
2139
- const parsedExtra = definition.message?.schema ? definition.message.schema.parse(extra) : {};
2140
2140
  const spaceRef = {
2141
2141
  ...msg.space,
2142
2142
  __platform: definition.name
@@ -2150,19 +2150,19 @@ async function Spectrum(options) {
2150
2150
  client,
2151
2151
  config
2152
2152
  });
2153
- const normalizedMessage = buildMessage({
2154
- id: msg.id,
2155
- content: msg.content,
2156
- sender: msg.sender,
2157
- timestamp: msg.timestamp ?? /* @__PURE__ */ new Date(),
2158
- extras: parsedExtra,
2159
- spaceRef,
2160
- space,
2161
- definition,
2153
+ const normalizedMessage = wrapProviderMessage(msg, {
2162
2154
  client,
2163
2155
  config,
2164
- direction: "inbound"
2156
+ definition,
2157
+ space,
2158
+ spaceRef
2165
2159
  });
2160
+ if (flattenGroups && normalizedMessage.content.type === "group") {
2161
+ for (const item of normalizedMessage.content.items) {
2162
+ yield [space, item];
2163
+ }
2164
+ continue;
2165
+ }
2166
2166
  yield [space, normalizedMessage];
2167
2167
  }
2168
2168
  };
@@ -2310,6 +2310,7 @@ export {
2310
2310
  custom,
2311
2311
  definePlatform,
2312
2312
  fromVCard,
2313
+ group,
2313
2314
  mergeStreams,
2314
2315
  reaction,
2315
2316
  resolveContents,
@@ -1,10 +1,10 @@
1
1
  import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
- import { AdvancedIMessage } from '@photon-ai/advanced-imessage';
3
- import { IMessageSDK } from '@photon-ai/imessage-kit';
2
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-D5KhSXLy.js';
3
+ import * as zod_v4_core from 'zod/v4/core';
4
4
  import * as z from 'zod';
5
5
  import z__default from 'zod';
6
- import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.js';
7
- import * as zod_v4_core from 'zod/v4/core';
6
+ import { AdvancedIMessage } from '@photon-ai/advanced-imessage';
7
+ import { IMessageSDK } from '@photon-ai/imessage-kit';
8
8
  import 'hotscript';
9
9
 
10
10
  type IMessageClient = IMessageSDK | AdvancedIMessage[];
@@ -16,7 +16,10 @@ declare const spaceSchema: z__default.ZodObject<{
16
16
  group: "group";
17
17
  }>;
18
18
  }, z__default.core.$strip>;
19
- type IMessageMessage = SchemaMessage<typeof userSchema, typeof spaceSchema>;
19
+ type IMessageMessage = SchemaMessage<typeof userSchema, typeof spaceSchema> & {
20
+ partIndex?: number;
21
+ parentId?: string;
22
+ };
20
23
 
21
24
  declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.ZodObject<{
22
25
  local: z.ZodLiteral<true>;
@@ -43,7 +46,10 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
43
46
  } | {
44
47
  id: string;
45
48
  type: "group";
46
- }, undefined, ProviderMessage<{
49
+ }, z.ZodObject<{
50
+ partIndex: z.ZodOptional<z.ZodNumber>;
51
+ parentId: z.ZodOptional<z.ZodString>;
52
+ }, zod_v4_core.$strip>, ProviderMessage<{
47
53
  id: string;
48
54
  }, {
49
55
  id: string;
@@ -51,7 +57,10 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
51
57
  } | {
52
58
  id: string;
53
59
  type: "group";
54
- }, Record<never, never>>, {
60
+ }, {
61
+ partIndex?: number | undefined;
62
+ parentId?: string | undefined;
63
+ }>, {
55
64
  messages: ({ client }: {
56
65
  client: IMessageClient;
57
66
  config: {
@@ -1,6 +1,7 @@
1
1
  import {
2
+ asGroup,
2
3
  asRichlink
3
- } from "../../chunk-6ZOLTQDN.js";
4
+ } from "../../chunk-HU2EOF3K.js";
4
5
  import {
5
6
  asAttachment,
6
7
  asContact,
@@ -11,12 +12,12 @@ import {
11
12
  mergeStreams,
12
13
  stream,
13
14
  toVCard
14
- } from "../../chunk-CZIWNTXP.js";
15
+ } from "../../chunk-OIXH5S65.js";
15
16
  import {
16
17
  UnsupportedError,
17
18
  asText,
18
19
  definePlatform
19
- } from "../../chunk-PLJI5FTO.js";
20
+ } from "../../chunk-U6WCQVVX.js";
20
21
 
21
22
  // src/providers/imessage/index.ts
22
23
  import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
@@ -249,6 +250,7 @@ var send = async (client, spaceId, content) => {
249
250
  throw UnsupportedError.content(content.type, "iMessage (local mode)");
250
251
  }
251
252
  };
253
+ var getMessage = async (_client, _id) => void 0;
252
254
 
253
255
  // src/providers/imessage/remote.ts
254
256
  import {
@@ -380,10 +382,52 @@ var ensureM4a = async (buffer, mimeType) => {
380
382
  return transcodeToM4a(buffer);
381
383
  };
382
384
 
385
+ // src/providers/imessage/cache.ts
386
+ var DEFAULT_MAX = 1e3;
387
+ var MessageCache = class {
388
+ map = /* @__PURE__ */ new Map();
389
+ max;
390
+ constructor(max = DEFAULT_MAX) {
391
+ this.max = max;
392
+ }
393
+ get(id) {
394
+ return this.map.get(id);
395
+ }
396
+ set(id, message) {
397
+ if (this.map.has(id)) {
398
+ this.map.delete(id);
399
+ }
400
+ this.map.set(id, message);
401
+ if (this.map.size > this.max) {
402
+ const first = this.map.keys().next().value;
403
+ if (first !== void 0) {
404
+ this.map.delete(first);
405
+ }
406
+ }
407
+ }
408
+ clear() {
409
+ this.map.clear();
410
+ }
411
+ };
412
+ var caches = /* @__PURE__ */ new WeakMap();
413
+ var getMessageCache = (owner) => {
414
+ let cache = caches.get(owner);
415
+ if (!cache) {
416
+ cache = new MessageCache();
417
+ caches.set(owner, cache);
418
+ }
419
+ return cache;
420
+ };
421
+
383
422
  // src/providers/imessage/remote.ts
384
423
  var PLATFORM = "iMessage";
385
424
  var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
386
- var unsupportedContent = (type) => UnsupportedError.content(type, PLATFORM);
425
+ var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
426
+ "attachment",
427
+ "contact",
428
+ "voice"
429
+ ]);
430
+ var unsupportedContent = (type, detail) => UnsupportedError.content(type, PLATFORM, detail);
387
431
  var toSendResult = (receipt) => ({
388
432
  id: receipt.guid,
389
433
  timestamp: /* @__PURE__ */ new Date()
@@ -442,14 +486,19 @@ var getAssociatedMessageType = (message) => {
442
486
  const fromRaw = raw?.associatedMessageType;
443
487
  return typeof fromRaw === "string" ? fromRaw : void 0;
444
488
  };
445
- var baseMessage = (event) => ({
446
- sender: { id: event.message.sender?.address ?? "" },
447
- space: {
448
- id: event.chatGuid,
449
- type: event.chatGuid.includes(";+;") ? "group" : "dm"
450
- },
451
- timestamp: event.timestamp
452
- });
489
+ var getBalloonBundleId = (message) => {
490
+ const raw = message._raw;
491
+ const id = raw?.balloonBundleId;
492
+ return typeof id === "string" ? id : void 0;
493
+ };
494
+ var resolveChatGuid = (message, hint) => {
495
+ if (hint) {
496
+ return hint;
497
+ }
498
+ const first = message.chatGuids?.[0];
499
+ return first ?? "";
500
+ };
501
+ var resolveSenderId = (message) => message.sender?.address ?? "";
453
502
  var toAttachmentContent2 = (client, info) => asAttachment({
454
503
  name: info.fileName,
455
504
  mimeType: info.mimeType,
@@ -465,10 +514,87 @@ var toVCardContent2 = async (client, info) => {
465
514
  return toAttachmentContent2(client, info);
466
515
  }
467
516
  };
468
- var getBalloonBundleId = (message) => {
469
- const raw = message._raw;
470
- const id = raw?.balloonBundleId;
471
- return typeof id === "string" ? id : void 0;
517
+ var attachmentContent = async (client, info) => isVCardAttachment2(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info);
518
+ var baseShape = (message, chatGuidHint, timestamp) => {
519
+ const chat = resolveChatGuid(message, chatGuidHint);
520
+ return {
521
+ sender: { id: resolveSenderId(message) },
522
+ space: {
523
+ id: chat,
524
+ type: chat.includes(";+;") ? "group" : "dm"
525
+ },
526
+ timestamp
527
+ };
528
+ };
529
+ var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId) => {
530
+ const content = await attachmentContent(client, info);
531
+ const msg = { ...base, id, content, partIndex };
532
+ if (parentId !== void 0) {
533
+ msg.parentId = parentId;
534
+ }
535
+ return msg;
536
+ };
537
+ var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
538
+ const messageGuidStr = message.guid;
539
+ const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
540
+ const base = baseShape(message, chatGuidHint, timestamp);
541
+ if (message.attachments.length === 1) {
542
+ const info = message.attachments[0];
543
+ if (!info) {
544
+ throw new Error("Unreachable: attachments.length === 1 but no element");
545
+ }
546
+ return buildAttachmentMessage(client, base, info, messageGuidStr, 0);
547
+ }
548
+ if (message.attachments.length > 1) {
549
+ const items = [];
550
+ for (let i = 0; i < message.attachments.length; i++) {
551
+ const info = message.attachments[i];
552
+ if (!info) {
553
+ continue;
554
+ }
555
+ items.push(
556
+ await buildAttachmentMessage(
557
+ client,
558
+ base,
559
+ info,
560
+ formatChildId(i, messageGuidStr),
561
+ i,
562
+ messageGuidStr
563
+ )
564
+ );
565
+ }
566
+ return {
567
+ ...base,
568
+ id: messageGuidStr,
569
+ content: asGroup({ items })
570
+ };
571
+ }
572
+ const text = message.text;
573
+ if (getBalloonBundleId(message) === URL_BALLOON_BUNDLE_ID) {
574
+ const url = text ?? "";
575
+ try {
576
+ return { ...base, id: messageGuidStr, content: asRichlink({ url }) };
577
+ } catch {
578
+ return {
579
+ ...base,
580
+ id: messageGuidStr,
581
+ content: url ? asText(url) : asCustom(message)
582
+ };
583
+ }
584
+ }
585
+ return {
586
+ ...base,
587
+ id: messageGuidStr,
588
+ content: text ? asText(text) : asCustom(message)
589
+ };
590
+ };
591
+ var cacheMessage = (cache, message) => {
592
+ cache.set(message.id, message);
593
+ if (message.content.type === "group") {
594
+ for (const item of message.content.items) {
595
+ cache.set(item.id, item);
596
+ }
597
+ }
472
598
  };
473
599
  var toRichlinkMessage = (event, base, id) => {
474
600
  const url = event.message.text ?? "";
@@ -482,8 +608,42 @@ var toRichlinkMessage = (event, base, id) => {
482
608
  };
483
609
  }
484
610
  };
485
- var PART_PREFIX = /^p:\d+\//;
486
- var toReactionMessage = (event, base, id, target) => {
611
+ var PART_PREFIX = /^p:(\d+)\//;
612
+ var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
613
+ var parseTapbackTarget = (target) => {
614
+ const match = target.match(PART_PREFIX);
615
+ const guid = target.replace(PART_PREFIX, "");
616
+ const partIndex = match ? Number(match[1]) : 0;
617
+ return { guid, partIndex };
618
+ };
619
+ var parseChildId = (id) => {
620
+ const match = id.match(PART_PREFIX);
621
+ if (!match) {
622
+ return null;
623
+ }
624
+ return {
625
+ parentGuid: id.replace(PART_PREFIX, ""),
626
+ partIndex: Number(match[1])
627
+ };
628
+ };
629
+ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
630
+ let candidate = cache.get(strippedGuid);
631
+ if (!candidate) {
632
+ try {
633
+ const fetched = await client.messages.get(messageGuid(strippedGuid));
634
+ candidate = await rebuildFromAppleMessage(client, fetched);
635
+ cacheMessage(cache, candidate);
636
+ } catch {
637
+ return;
638
+ }
639
+ }
640
+ if (candidate.content.type === "group") {
641
+ const item = candidate.content.items[partIndex];
642
+ return item ?? candidate;
643
+ }
644
+ return candidate;
645
+ };
646
+ var toReactionMessage = async (client, cache, event, base, id, target) => {
487
647
  const type = getAssociatedMessageType(event.message);
488
648
  if (type && isTapbackRemoval(type)) {
489
649
  return [];
@@ -495,41 +655,89 @@ var toReactionMessage = (event, base, id, target) => {
495
655
  if (!emoji) {
496
656
  return [];
497
657
  }
498
- const normalizedTarget = target.replace(PART_PREFIX, "");
658
+ const { guid: strippedGuid, partIndex } = parseTapbackTarget(target);
659
+ const resolved = await resolveReactionTarget(
660
+ client,
661
+ cache,
662
+ strippedGuid,
663
+ partIndex
664
+ );
665
+ if (!resolved) {
666
+ return [];
667
+ }
499
668
  return [
500
- { ...base, id, content: asReaction({ emoji, target: normalizedTarget }) }
669
+ {
670
+ ...base,
671
+ id,
672
+ content: asReaction({ emoji, target: resolved })
673
+ }
501
674
  ];
502
675
  };
503
- var toMessages2 = async (client, event) => {
504
- const base = baseMessage(event);
676
+ var toMessages2 = async (client, cache, event) => {
677
+ const base = baseShape(event.message, event.chatGuid, event.timestamp);
505
678
  const messageGuidStr = event.message.guid;
506
679
  const assoc = event.message.associatedMessageGuid;
507
680
  if (assoc) {
508
- return toReactionMessage(event, base, messageGuidStr, assoc);
681
+ return toReactionMessage(client, cache, event, base, messageGuidStr, assoc);
509
682
  }
510
683
  if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
511
- return [toRichlinkMessage(event, base, messageGuidStr)];
684
+ const msg2 = toRichlinkMessage(event, base, messageGuidStr);
685
+ cacheMessage(cache, msg2);
686
+ return [msg2];
512
687
  }
513
- if (event.message.attachments.length > 0) {
514
- return Promise.all(
515
- event.message.attachments.map(async (info) => ({
516
- ...base,
517
- id: `${messageGuidStr}:${info.guid}`,
518
- content: isVCardAttachment2(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info)
519
- }))
688
+ if (event.message.attachments.length === 1) {
689
+ const info = event.message.attachments[0];
690
+ if (!info) {
691
+ throw new Error("Unreachable: attachments.length === 1 but no element");
692
+ }
693
+ const msg2 = await buildAttachmentMessage(
694
+ client,
695
+ base,
696
+ info,
697
+ messageGuidStr,
698
+ 0
520
699
  );
700
+ cacheMessage(cache, msg2);
701
+ return [msg2];
521
702
  }
522
- const text = event.message.text;
523
- return [
524
- {
703
+ if (event.message.attachments.length > 1) {
704
+ const items = [];
705
+ for (let i = 0; i < event.message.attachments.length; i++) {
706
+ const info = event.message.attachments[i];
707
+ if (!info) {
708
+ continue;
709
+ }
710
+ items.push(
711
+ await buildAttachmentMessage(
712
+ client,
713
+ base,
714
+ info,
715
+ formatChildId(i, messageGuidStr),
716
+ i,
717
+ messageGuidStr
718
+ )
719
+ );
720
+ }
721
+ const parent = {
525
722
  ...base,
526
723
  id: messageGuidStr,
527
- content: text ? asText(text) : asCustom(event.message)
528
- }
529
- ];
724
+ content: asGroup({ items })
725
+ };
726
+ cacheMessage(cache, parent);
727
+ return [parent];
728
+ }
729
+ const text = event.message.text;
730
+ const msg = {
731
+ ...base,
732
+ id: messageGuidStr,
733
+ content: text ? asText(text) : asCustom(event.message)
734
+ };
735
+ cacheMessage(cache, msg);
736
+ return [msg];
530
737
  };
531
738
  var clientStream = (client) => {
532
739
  const sub = client.messages.subscribe("message.received");
740
+ const cache = getMessageCache(client);
533
741
  return stream((emit, end) => {
534
742
  const pump = (async () => {
535
743
  try {
@@ -537,7 +745,7 @@ var clientStream = (client) => {
537
745
  if (event.message.isFromMe) {
538
746
  continue;
539
747
  }
540
- for (const message of await toMessages2(client, event)) {
748
+ for (const message of await toMessages2(client, cache, event)) {
541
749
  await emit(message);
542
750
  }
543
751
  }
@@ -581,12 +789,7 @@ var stopTyping = async (clients, spaceId) => {
581
789
  }
582
790
  await remote.chats.stopTyping(chatGuid(spaceId));
583
791
  };
584
- var send2 = async (clients, spaceId, content) => {
585
- const remote = clients[0];
586
- if (!remote) {
587
- throw new Error("No remote iMessage client available");
588
- }
589
- const chat = chatGuid(spaceId);
792
+ var sendSingle = async (remote, chat, content) => {
590
793
  switch (content.type) {
591
794
  case "text":
592
795
  return toSendResult(await remote.messages.send(chat, content.text));
@@ -601,9 +804,7 @@ var send2 = async (clients, spaceId, content) => {
601
804
  mimeType: content.mimeType
602
805
  });
603
806
  return toSendResult(
604
- await remote.messages.send(chat, "", {
605
- attachment: attachment.guid
606
- })
807
+ await remote.messages.send(chat, "", { attachment: attachment.guid })
607
808
  );
608
809
  }
609
810
  case "contact": {
@@ -631,6 +832,34 @@ var send2 = async (clients, spaceId, content) => {
631
832
  throw unsupportedContent(content.type);
632
833
  }
633
834
  };
835
+ var send2 = async (clients, spaceId, content) => {
836
+ const remote = clients[0];
837
+ if (!remote) {
838
+ throw new Error("No remote iMessage client available");
839
+ }
840
+ const chat = chatGuid(spaceId);
841
+ if (content.type === "group") {
842
+ for (const sub of content.items) {
843
+ const itemType = sub.content.type;
844
+ if (!GROUP_ITEM_ALLOWED.has(itemType)) {
845
+ throw unsupportedContent(
846
+ "group",
847
+ `"${itemType}" items are not supported inside a group`
848
+ );
849
+ }
850
+ }
851
+ const groupMembers = [];
852
+ for (const sub of content.items) {
853
+ groupMembers.push(await sendSingle(remote, chat, sub.content));
854
+ }
855
+ const first = groupMembers[0];
856
+ if (!first) {
857
+ throw new Error("Empty group");
858
+ }
859
+ return { ...first, groupMembers };
860
+ }
861
+ return sendSingle(remote, chat, content);
862
+ };
634
863
  var replyToMessage = async (clients, spaceId, msgId, content) => {
635
864
  const remote = clients[0];
636
865
  if (!remote) {
@@ -709,18 +938,56 @@ var editMessage = async (clients, spaceId, msgId, content) => {
709
938
  content.text
710
939
  );
711
940
  };
712
- var reactToMessage = async (clients, spaceId, msgId, reaction) => {
941
+ var reactToMessage = async (clients, spaceId, target, reaction) => {
713
942
  const remote = clients[0];
714
943
  if (!remote) {
715
944
  return;
716
945
  }
717
946
  const chat = chatGuid(spaceId);
718
- const msg = messageGuid(msgId);
947
+ const parentGuid = target.parentId ?? target.id;
948
+ const guid = messageGuid(parentGuid);
949
+ const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
719
950
  const native = EMOJI_TO_TAPBACK[reaction];
720
951
  if (native) {
721
- await remote.messages.react(chat, msg, native);
952
+ await remote.messages.react(chat, guid, native, opts);
722
953
  } else {
723
- await remote.messages.reactEmoji(chat, msg, reaction);
954
+ await remote.messages.reactEmoji(chat, guid, reaction, opts);
955
+ }
956
+ };
957
+ var getMessage2 = async (clients, spaceId, msgId) => {
958
+ const remote = clients[0];
959
+ if (!remote) {
960
+ return;
961
+ }
962
+ const cache = getMessageCache(remote);
963
+ const cached = cache.get(msgId);
964
+ if (cached) {
965
+ return cached;
966
+ }
967
+ const childRef = parseChildId(msgId);
968
+ if (childRef) {
969
+ try {
970
+ const fetched = await remote.messages.get(
971
+ messageGuid(childRef.parentGuid)
972
+ );
973
+ const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
974
+ cacheMessage(cache, parent);
975
+ if (parent.content.type !== "group") {
976
+ return;
977
+ }
978
+ const items = parent.content.items;
979
+ return items[childRef.partIndex];
980
+ } catch {
981
+ return;
982
+ }
983
+ }
984
+ try {
985
+ const fetched = await remote.messages.get(messageGuid(msgId));
986
+ const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
987
+ cacheMessage(cache, rebuilt);
988
+ return rebuilt;
989
+ } catch {
990
+ return;
724
991
  }
725
992
  };
726
993
 
@@ -741,6 +1008,10 @@ var spaceSchema = z.object({
741
1008
  id: z.string(),
742
1009
  type: z.enum(["dm", "group"])
743
1010
  });
1011
+ var messageSchema = z.object({
1012
+ partIndex: z.number().int().nonnegative().optional(),
1013
+ parentId: z.string().optional()
1014
+ });
744
1015
 
745
1016
  // src/providers/imessage/index.ts
746
1017
  var imessage = definePlatform("iMessage", {
@@ -776,6 +1047,9 @@ var imessage = definePlatform("iMessage", {
776
1047
  return { id: chat.guid, type: "group" };
777
1048
  }
778
1049
  },
1050
+ message: {
1051
+ schema: messageSchema
1052
+ },
779
1053
  lifecycle: {
780
1054
  createClient: async ({
781
1055
  config,
@@ -829,11 +1103,16 @@ var imessage = definePlatform("iMessage", {
829
1103
  }
830
1104
  await stopTyping(client, space.id);
831
1105
  },
832
- reactToMessage: async ({ space, messageId, reaction, client }) => {
1106
+ reactToMessage: async ({ space, target, reaction, client }) => {
833
1107
  if (isLocal(client)) {
834
1108
  throw UnsupportedError.action("react", "iMessage (local mode)");
835
1109
  }
836
- await reactToMessage(client, space.id, messageId, reaction);
1110
+ await reactToMessage(
1111
+ client,
1112
+ space.id,
1113
+ target,
1114
+ reaction
1115
+ );
837
1116
  },
838
1117
  replyToMessage: async ({ space, messageId, content, client }) => {
839
1118
  if (isLocal(client)) {
@@ -846,6 +1125,12 @@ var imessage = definePlatform("iMessage", {
846
1125
  throw UnsupportedError.action("edit", "iMessage (local mode)");
847
1126
  }
848
1127
  await editMessage(client, space.id, messageId, content);
1128
+ },
1129
+ getMessage: async ({ space, messageId, client }) => {
1130
+ if (isLocal(client)) {
1131
+ return getMessage(client, messageId);
1132
+ }
1133
+ return getMessage2(client, space.id, messageId);
849
1134
  }
850
1135
  }
851
1136
  });
@@ -1,4 +1,4 @@
1
- import { d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.js';
1
+ import { d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-D5KhSXLy.js';
2
2
  import * as node_readline from 'node:readline';
3
3
  import z__default from 'zod';
4
4
  import 'hotscript';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  UnsupportedError,
3
3
  definePlatform
4
- } from "../../chunk-PLJI5FTO.js";
4
+ } from "../../chunk-U6WCQVVX.js";
5
5
 
6
6
  // src/providers/terminal/index.ts
7
7
  import { createInterface } from "readline";
@@ -2,7 +2,7 @@ import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
2
  import { WhatsAppClient } from '@photon-ai/whatsapp-business';
3
3
  import * as z from 'zod';
4
4
  import z__default from 'zod';
5
- import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.js';
5
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-D5KhSXLy.js';
6
6
  import * as zod_v4_core from 'zod/v4/core';
7
7
  import 'hotscript';
8
8
 
@@ -6,12 +6,12 @@ import {
6
6
  cloud,
7
7
  mergeStreams,
8
8
  stream
9
- } from "../../chunk-CZIWNTXP.js";
9
+ } from "../../chunk-OIXH5S65.js";
10
10
  import {
11
11
  UnsupportedError,
12
12
  asText,
13
13
  definePlatform
14
- } from "../../chunk-PLJI5FTO.js";
14
+ } from "../../chunk-U6WCQVVX.js";
15
15
 
16
16
  // src/providers/whatsapp-business/index.ts
17
17
  import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
@@ -401,11 +401,16 @@ var mapContent = (client, content) => {
401
401
  return asCustom({ whatsapp_type: "sticker", ...content.sticker });
402
402
  case "location":
403
403
  return asCustom({ whatsapp_type: "location", ...content.location });
404
- case "reaction":
404
+ case "reaction": {
405
+ const stubTarget = {
406
+ id: content.reaction.messageId,
407
+ content: asCustom({ whatsapp_type: "reaction-target", stub: true })
408
+ };
405
409
  return asReaction({
406
410
  emoji: content.reaction.emoji,
407
- target: content.reaction.messageId
411
+ target: stubTarget
408
412
  });
413
+ }
409
414
  case "interactive":
410
415
  return asCustom({ whatsapp_type: "interactive", ...content.interactive });
411
416
  case "button":
@@ -723,11 +728,11 @@ var whatsappBusiness = definePlatform("WhatsApp Business", {
723
728
  send: async ({ space, content, client }) => {
724
729
  return await send(client, space.id, content);
725
730
  },
726
- reactToMessage: async ({ space, messageId, reaction, client }) => {
731
+ reactToMessage: async ({ space, target, reaction, client }) => {
727
732
  await reactToMessage(
728
733
  client,
729
734
  space.id,
730
- messageId,
735
+ target.id,
731
736
  reaction
732
737
  );
733
738
  },
@@ -91,7 +91,10 @@ declare const contentSchema: z__default.ZodDiscriminatedUnion<[z__default.ZodObj
91
91
  }, z__default.core.$strip>, z__default.ZodObject<{
92
92
  type: z__default.ZodLiteral<"reaction">;
93
93
  emoji: z__default.ZodString;
94
- target: z__default.ZodString;
94
+ target: z__default.ZodCustom<Message, Message>;
95
+ }, z__default.core.$strip>, z__default.ZodObject<{
96
+ type: z__default.ZodLiteral<"group">;
97
+ items: z__default.ZodArray<z__default.ZodCustom<Message, Message>>;
95
98
  }, z__default.core.$strip>], "type">;
96
99
  type Content = z__default.infer<typeof contentSchema>;
97
100
  interface ContentBuilder {
@@ -107,6 +110,13 @@ interface User {
107
110
  interface Space<_Def = unknown> {
108
111
  readonly __platform: string;
109
112
  edit(message: OutboundMessage, newContent: ContentInput): Promise<void>;
113
+ /**
114
+ * Look up a message in this space by its id. Returns `undefined` if the
115
+ * platform has no way to resolve the id (e.g. cache miss with no by-id
116
+ * SDK fallback). Used to materialize a `Message` for APIs that require one,
117
+ * such as `reaction()`.
118
+ */
119
+ getMessage(id: string): Promise<Message | undefined>;
110
120
  readonly id: string;
111
121
  responding<T>(fn: () => T | Promise<T>): Promise<T>;
112
122
  send(content: ContentInput): Promise<OutboundMessage | undefined>;
@@ -158,6 +168,14 @@ type ProviderMessage<TSender extends ResolvedUser = ResolvedUser, TSpace extends
158
168
  timestamp?: Date;
159
169
  } & TExtra;
160
170
  interface SendResult<TSender extends ResolvedUser = ResolvedUser> {
171
+ /**
172
+ * Per-item send receipts returned when the dispatched content was a
173
+ * `group`. Providers that iterate native sends to emulate a group
174
+ * (e.g. iMessage) populate this so the platform build layer can
175
+ * replace the outbound group's placeholder items with real Messages
176
+ * that carry each item's own id.
177
+ */
178
+ groupMembers?: SendResult<TSender>[];
161
179
  id: string;
162
180
  sender?: TSender;
163
181
  timestamp?: Date;
@@ -190,7 +208,7 @@ interface PlatformDef<_Name extends string = string, _ConfigSchema extends z__de
190
208
  }) => Promise<void>;
191
209
  reactToMessage?: (_: {
192
210
  space: _ResolvedSpace & SpaceRef;
193
- messageId: string;
211
+ target: _MessageType;
194
212
  reaction: string;
195
213
  client: _Client;
196
214
  config: z__default.infer<_ConfigSchema>;
@@ -209,6 +227,12 @@ interface PlatformDef<_Name extends string = string, _ConfigSchema extends z__de
209
227
  client: _Client;
210
228
  config: z__default.infer<_ConfigSchema>;
211
229
  }) => Promise<void>;
230
+ getMessage?: (_: {
231
+ space: _ResolvedSpace & SpaceRef;
232
+ messageId: string;
233
+ client: _Client;
234
+ config: z__default.infer<_ConfigSchema>;
235
+ }) => Promise<_MessageType | undefined>;
212
236
  };
213
237
  config: _ConfigSchema;
214
238
  events: _Events;
@@ -259,6 +283,7 @@ interface AnyPlatformDef {
259
283
  reactToMessage?: (_: any) => Promise<void>;
260
284
  replyToMessage?: (_: any) => Promise<SendResult>;
261
285
  editMessage?: (_: any) => Promise<void>;
286
+ getMessage?: (_: any) => Promise<any>;
262
287
  };
263
288
  config: z__default.ZodType<object>;
264
289
  events: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrum-ts",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",