spectrum-ts 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,58 @@
1
+ // src/content/poll.ts
2
+ import z from "zod";
3
+ var pollChoiceSchema = z.object({
4
+ title: z.string().nonempty()
5
+ });
6
+ var pollSchema = z.object({
7
+ type: z.literal("poll"),
8
+ title: z.string().nonempty().max(300),
9
+ options: z.array(pollChoiceSchema).min(2).max(10)
10
+ });
11
+ var pollOptionSchema = z.object({
12
+ type: z.literal("poll_option"),
13
+ option: pollChoiceSchema,
14
+ poll: pollSchema,
15
+ selected: z.boolean(),
16
+ title: z.string().nonempty()
17
+ }).superRefine((value, ctx) => {
18
+ if (value.title !== value.option.title) {
19
+ ctx.addIssue({
20
+ code: "custom",
21
+ message: "poll_option title must match option.title",
22
+ path: ["title"]
23
+ });
24
+ }
25
+ if (!value.poll.options.some(
26
+ (pollOption) => pollOption.title === value.option.title
27
+ )) {
28
+ ctx.addIssue({
29
+ code: "custom",
30
+ message: "poll_option option must exist in poll.options",
31
+ path: ["option"]
32
+ });
33
+ }
34
+ });
35
+ var asPoll = (input) => pollSchema.parse({ type: "poll", ...input });
36
+ var asPollOption = (input) => pollOptionSchema.parse({
37
+ type: "poll_option",
38
+ ...input,
39
+ title: input.option.title
40
+ });
41
+ var option = (title) => ({ title });
42
+ var normalize = (raw) => typeof raw === "string" ? { title: raw } : { title: raw.title };
43
+ var collectOptions = (args) => {
44
+ const [first] = args;
45
+ if (args.length === 1 && Array.isArray(first)) {
46
+ return first;
47
+ }
48
+ return args;
49
+ };
50
+ function poll(title, ...rest) {
51
+ return {
52
+ build: async () => asPoll({ title, options: collectOptions(rest).map(normalize) })
53
+ };
54
+ }
55
+
1
56
  // src/utils/stream.ts
2
57
  import { Repeater } from "@repeaterjs/repeater";
3
58
  function stream(setup) {
@@ -122,6 +177,10 @@ var cloud = {
122
177
  };
123
178
 
124
179
  export {
180
+ asPoll,
181
+ asPollOption,
182
+ option,
183
+ poll,
125
184
  stream,
126
185
  mergeStreams,
127
186
  SpectrumCloudError,
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-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';
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-DJQLFwWW.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-DJQLFwWW.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';
@@ -138,6 +138,41 @@ declare const groupSchema: z__default.ZodObject<{
138
138
  type Group = z__default.infer<typeof groupSchema>;
139
139
  declare function group(...items: [ContentInput, ContentInput, ...ContentInput[]]): ContentBuilder;
140
140
 
141
+ declare const pollChoiceSchema: z__default.ZodObject<{
142
+ title: z__default.ZodString;
143
+ }, z__default.core.$strip>;
144
+ declare const pollSchema: z__default.ZodObject<{
145
+ type: z__default.ZodLiteral<"poll">;
146
+ title: z__default.ZodString;
147
+ options: z__default.ZodArray<z__default.ZodObject<{
148
+ title: z__default.ZodString;
149
+ }, z__default.core.$strip>>;
150
+ }, z__default.core.$strip>;
151
+ declare const pollOptionSchema: z__default.ZodObject<{
152
+ type: z__default.ZodLiteral<"poll_option">;
153
+ option: z__default.ZodObject<{
154
+ title: z__default.ZodString;
155
+ }, z__default.core.$strip>;
156
+ poll: z__default.ZodObject<{
157
+ type: z__default.ZodLiteral<"poll">;
158
+ title: z__default.ZodString;
159
+ options: z__default.ZodArray<z__default.ZodObject<{
160
+ title: z__default.ZodString;
161
+ }, z__default.core.$strip>>;
162
+ }, z__default.core.$strip>;
163
+ selected: z__default.ZodBoolean;
164
+ title: z__default.ZodString;
165
+ }, z__default.core.$strip>;
166
+ type Poll = z__default.infer<typeof pollSchema>;
167
+ type PollChoice = z__default.infer<typeof pollChoiceSchema>;
168
+ type PollOption = z__default.infer<typeof pollOptionSchema>;
169
+ type PollChoiceInput = string | {
170
+ title: string;
171
+ };
172
+ declare const option: (title: string) => PollChoice;
173
+ declare function poll(title: string, options: PollChoiceInput[]): ContentBuilder;
174
+ declare function poll(title: string, ...options: PollChoiceInput[]): ContentBuilder;
175
+
141
176
  declare const reactionSchema: z__default.ZodObject<{
142
177
  type: z__default.ZodLiteral<"reaction">;
143
178
  emoji: z__default.ZodString;
@@ -2234,4 +2269,4 @@ declare class UnsupportedError extends Error {
2234
2269
  declare const fromVCard: (vcf: string) => ContactInput;
2235
2270
  declare const toVCard: (contact: Contact) => Promise<string>;
2236
2271
 
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 };
2272
+ 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 Poll, type PollChoice, type PollChoiceInput, type PollOption, 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, option, poll, reaction, resolveContents, richlink, text, toVCard, voice };
package/dist/index.js CHANGED
@@ -9,8 +9,10 @@ import {
9
9
  SpectrumCloudError,
10
10
  cloud,
11
11
  mergeStreams,
12
+ option,
13
+ poll,
12
14
  stream
13
- } from "./chunk-2Y5GBI6W.js";
15
+ } from "./chunk-PXX7ISZ6.js";
14
16
  import {
15
17
  UnsupportedError,
16
18
  attachment,
@@ -2205,6 +2207,8 @@ export {
2205
2207
  fromVCard,
2206
2208
  group,
2207
2209
  mergeStreams,
2210
+ option,
2211
+ poll,
2208
2212
  reaction,
2209
2213
  resolveContents,
2210
2214
  richlink,
@@ -1,5 +1,5 @@
1
1
  import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
- import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-D5KhSXLy.js';
2
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-DJQLFwWW.js';
3
3
  import * as zod_v4_core from 'zod/v4/core';
4
4
  import * as z from 'zod';
5
5
  import z__default from 'zod';
@@ -3,10 +3,12 @@ import {
3
3
  asRichlink
4
4
  } from "../../chunk-LAGNM6I7.js";
5
5
  import {
6
+ asPoll,
7
+ asPollOption,
6
8
  cloud,
7
9
  mergeStreams,
8
10
  stream
9
- } from "../../chunk-2Y5GBI6W.js";
11
+ } from "../../chunk-PXX7ISZ6.js";
10
12
  import {
11
13
  UnsupportedError,
12
14
  asAttachment,
@@ -246,6 +248,8 @@ var send = async (client, spaceId, content) => {
246
248
  );
247
249
  return synthSendResult();
248
250
  }
251
+ case "poll":
252
+ throw UnsupportedError.content("poll", "iMessage (local mode)");
249
253
  default:
250
254
  throw UnsupportedError.content(content.type, "iMessage (local mode)");
251
255
  }
@@ -409,12 +413,98 @@ var MessageCache = class {
409
413
  this.map.clear();
410
414
  }
411
415
  };
412
- var caches = /* @__PURE__ */ new WeakMap();
416
+ var PollCache = class {
417
+ map = /* @__PURE__ */ new Map();
418
+ max;
419
+ selectionEventTimesByPoll = /* @__PURE__ */ new Map();
420
+ selectionsByPoll = /* @__PURE__ */ new Map();
421
+ constructor(max = DEFAULT_MAX) {
422
+ this.max = max;
423
+ }
424
+ get(id) {
425
+ return this.map.get(id);
426
+ }
427
+ set(id, poll) {
428
+ if (this.map.has(id)) {
429
+ this.map.delete(id);
430
+ }
431
+ this.map.set(id, poll);
432
+ if (this.map.size > this.max) {
433
+ const first = this.map.keys().next().value;
434
+ if (first !== void 0) {
435
+ this.map.delete(first);
436
+ this.selectionEventTimesByPoll.delete(first);
437
+ this.selectionsByPoll.delete(first);
438
+ }
439
+ }
440
+ }
441
+ clear() {
442
+ this.map.clear();
443
+ this.selectionEventTimesByPoll.clear();
444
+ this.selectionsByPoll.clear();
445
+ }
446
+ actorSelectionDeltas(pollId, actorId, optionIds) {
447
+ const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
448
+ if (!previous) {
449
+ return optionIds.map((optionId) => ({ optionId, selected: true }));
450
+ }
451
+ const current = new Set(optionIds);
452
+ const selected = optionIds.filter((optionId) => !previous.has(optionId)).map((optionId) => ({ optionId, selected: true }));
453
+ const deselected = [...previous].filter((optionId) => !current.has(optionId)).map((optionId) => ({ optionId, selected: false }));
454
+ return [...selected, ...deselected];
455
+ }
456
+ clearedActorSelectionDeltas(pollId, actorId) {
457
+ const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
458
+ if (!previous) {
459
+ return [];
460
+ }
461
+ return [...previous].map((optionId) => ({ optionId, selected: false }));
462
+ }
463
+ actorSelection(pollId, actorId) {
464
+ const selection = this.selectionsByPoll.get(pollId)?.get(actorId);
465
+ return selection ? [...selection] : void 0;
466
+ }
467
+ commitActorSelection(pollId, actorId, optionIds, at) {
468
+ let selections = this.selectionsByPoll.get(pollId);
469
+ if (!selections) {
470
+ selections = /* @__PURE__ */ new Map();
471
+ this.selectionsByPoll.set(pollId, selections);
472
+ }
473
+ selections.set(actorId, new Set(optionIds));
474
+ if (!at) {
475
+ return;
476
+ }
477
+ let eventTimes = this.selectionEventTimesByPoll.get(pollId);
478
+ if (!eventTimes) {
479
+ eventTimes = /* @__PURE__ */ new Map();
480
+ this.selectionEventTimesByPoll.set(pollId, eventTimes);
481
+ }
482
+ const eventTime = at.getTime();
483
+ const previousTime = eventTimes.get(actorId);
484
+ if (previousTime === void 0 || eventTime >= previousTime) {
485
+ eventTimes.set(actorId, eventTime);
486
+ }
487
+ }
488
+ isStaleActorSelectionEvent(pollId, actorId, at) {
489
+ const previousTime = this.selectionEventTimesByPoll.get(pollId)?.get(actorId);
490
+ return previousTime !== void 0 && at.getTime() < previousTime;
491
+ }
492
+ };
493
+ var messageCaches = /* @__PURE__ */ new WeakMap();
494
+ var pollCaches = /* @__PURE__ */ new WeakMap();
413
495
  var getMessageCache = (owner) => {
414
- let cache = caches.get(owner);
496
+ let cache = messageCaches.get(owner);
415
497
  if (!cache) {
416
498
  cache = new MessageCache();
417
- caches.set(owner, cache);
499
+ messageCaches.set(owner, cache);
500
+ }
501
+ return cache;
502
+ };
503
+ var getPollCache = (owner) => {
504
+ let cache = pollCaches.get(owner);
505
+ if (!cache) {
506
+ cache = new PollCache();
507
+ pollCaches.set(owner, cache);
418
508
  }
419
509
  return cache;
420
510
  };
@@ -735,13 +825,222 @@ var toMessages2 = async (client, cache, event) => {
735
825
  cacheMessage(cache, msg);
736
826
  return [msg];
737
827
  };
738
- var clientStream = (client) => {
739
- const sub = client.messages.subscribe("message.received");
828
+ var isVotedPollEvent = (event) => event.delta.type === "voted";
829
+ var isUnvotedPollEvent = (event) => event.delta.type === "unvoted";
830
+ var toCachedPoll = (input) => {
831
+ const poll = asPoll({
832
+ title: input.title,
833
+ options: input.options.map((optionInfo) => ({
834
+ title: optionInfo.text
835
+ }))
836
+ });
837
+ const optionsByIdentifier = /* @__PURE__ */ new Map();
838
+ for (const [index, optionInfo] of input.options.entries()) {
839
+ const option = poll.options[index];
840
+ if (option && optionInfo.optionIdentifier) {
841
+ optionsByIdentifier.set(optionInfo.optionIdentifier, option);
842
+ }
843
+ }
844
+ return { poll, optionsByIdentifier };
845
+ };
846
+ var cachePollInfo = (cache, info) => {
847
+ const cached = toCachedPoll(info);
848
+ cache.set(info.messageGuid, cached);
849
+ return cached;
850
+ };
851
+ var cachePollEvent = (cache, event) => {
852
+ if (event.delta.type === "created" || event.delta.type === "optionAdded") {
853
+ try {
854
+ const cached = toCachedPoll({
855
+ title: event.delta.title,
856
+ options: event.delta.options
857
+ });
858
+ cache.set(event.pollMessageGuid, cached);
859
+ return cached;
860
+ } catch (e) {
861
+ console.error("[spectrum-ts][imessage][poll] failed to cache poll", e);
862
+ }
863
+ }
864
+ };
865
+ var fetchPollInfo = async (client, cache, event) => {
866
+ try {
867
+ const info = await client.polls.get(event.pollMessageGuid);
868
+ cachePollInfo(cache, info);
869
+ return info;
870
+ } catch (e) {
871
+ console.error("[spectrum-ts][imessage][poll] failed to fetch poll", e);
872
+ return;
873
+ }
874
+ };
875
+ var resolvePoll = async (client, cache, event) => {
876
+ const pollId = event.pollMessageGuid;
877
+ const cached = cache.get(pollId);
878
+ if (cached) {
879
+ return cached;
880
+ }
881
+ try {
882
+ const info = await client.polls.get(event.pollMessageGuid);
883
+ return cachePollInfo(cache, info);
884
+ } catch (e) {
885
+ console.error("[spectrum-ts][imessage][poll] failed to resolve poll", e);
886
+ return;
887
+ }
888
+ };
889
+ var buildPollOptionMessage = (input) => {
890
+ const option = input.cached.optionsByIdentifier.get(input.optionId);
891
+ if (!option) {
892
+ return;
893
+ }
894
+ const action = input.selected ? "selected" : "deselected";
895
+ return {
896
+ id: `${input.event.pollMessageGuid}:${input.senderAddress}:${input.optionId}:${action}:${input.event.at.getTime()}`,
897
+ sender: { id: input.senderAddress },
898
+ space: {
899
+ id: input.chatGuid,
900
+ type: input.chatGuid.includes(";+;") ? "group" : "dm"
901
+ },
902
+ timestamp: input.event.at,
903
+ content: asPollOption({
904
+ option,
905
+ poll: input.cached.poll,
906
+ selected: input.selected
907
+ })
908
+ };
909
+ };
910
+ var buildPollOptionMessages = (input) => {
911
+ const messages3 = [];
912
+ for (const delta of input.deltas) {
913
+ const message = buildPollOptionMessage({
914
+ cached: input.cached,
915
+ chatGuid: input.chatGuid,
916
+ event: input.event,
917
+ optionId: delta.optionId,
918
+ selected: delta.selected,
919
+ senderAddress: input.senderAddress
920
+ });
921
+ if (message) {
922
+ messages3.push(message);
923
+ }
924
+ }
925
+ return messages3;
926
+ };
927
+ var allOptionIdsKnown = (cached, optionIds) => optionIds.every((optionId) => cached.optionsByIdentifier.has(optionId));
928
+ var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) => {
929
+ const info = await fetchPollInfo(client, pollCache, event);
930
+ if (!info) {
931
+ return;
932
+ }
933
+ const refreshed = pollCache.get(info.messageGuid);
934
+ if (!refreshed) {
935
+ return;
936
+ }
937
+ return {
938
+ optionIds: [...fallbackOptionIds],
939
+ poll: refreshed
940
+ };
941
+ };
942
+ var toPollVoteMessages = async (client, pollCache, event) => {
943
+ const senderAddress = event.actor.address;
944
+ if (!senderAddress) {
945
+ return [];
946
+ }
947
+ const pollId = event.pollMessageGuid;
948
+ if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
949
+ return [];
950
+ }
951
+ const cached = await resolvePoll(client, pollCache, event);
952
+ if (!cached) {
953
+ return [];
954
+ }
955
+ const chatGuidStr = event.chatGuid;
956
+ let currentOptionIds = [...event.delta.optionIdentifiers];
957
+ let resolvedPoll = cached;
958
+ if (currentOptionIds.some(
959
+ (optionId) => !resolvedPoll.optionsByIdentifier.has(optionId)
960
+ )) {
961
+ const snapshot = await refreshPollMetadata(
962
+ client,
963
+ pollCache,
964
+ event,
965
+ currentOptionIds
966
+ );
967
+ if (snapshot) {
968
+ currentOptionIds = snapshot.optionIds;
969
+ resolvedPoll = snapshot.poll;
970
+ }
971
+ }
972
+ if (!allOptionIdsKnown(resolvedPoll, currentOptionIds)) {
973
+ return [];
974
+ }
975
+ const deltas = pollCache.actorSelectionDeltas(
976
+ pollId,
977
+ senderAddress,
978
+ currentOptionIds
979
+ );
980
+ const messages3 = buildPollOptionMessages({
981
+ cached: resolvedPoll,
982
+ chatGuid: chatGuidStr,
983
+ deltas,
984
+ event,
985
+ senderAddress
986
+ });
987
+ pollCache.commitActorSelection(
988
+ pollId,
989
+ senderAddress,
990
+ currentOptionIds,
991
+ event.at
992
+ );
993
+ return messages3;
994
+ };
995
+ var toPollUnvoteMessages = async (client, pollCache, event) => {
996
+ const senderAddress = event.actor.address;
997
+ if (!senderAddress) {
998
+ return [];
999
+ }
1000
+ const pollId = event.pollMessageGuid;
1001
+ if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
1002
+ return [];
1003
+ }
1004
+ const cached = await resolvePoll(client, pollCache, event);
1005
+ if (!cached) {
1006
+ return [];
1007
+ }
1008
+ const chatGuidStr = event.chatGuid;
1009
+ const messages3 = [];
1010
+ const deltas = pollCache.clearedActorSelectionDeltas(pollId, senderAddress);
1011
+ for (const delta of deltas) {
1012
+ const message = buildPollOptionMessage({
1013
+ cached,
1014
+ chatGuid: chatGuidStr,
1015
+ event,
1016
+ optionId: delta.optionId,
1017
+ selected: delta.selected,
1018
+ senderAddress
1019
+ });
1020
+ if (message) {
1021
+ messages3.push(message);
1022
+ }
1023
+ }
1024
+ pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
1025
+ return messages3;
1026
+ };
1027
+ var toPollDeltaMessages = async (client, pollCache, event) => {
1028
+ if (isVotedPollEvent(event)) {
1029
+ return toPollVoteMessages(client, pollCache, event);
1030
+ }
1031
+ if (isUnvotedPollEvent(event)) {
1032
+ return toPollUnvoteMessages(client, pollCache, event);
1033
+ }
1034
+ return [];
1035
+ };
1036
+ var clientStream = (client, pollCache) => {
1037
+ const messageSub = client.messages.subscribe("message.received");
1038
+ const pollSub = client.polls.subscribe();
740
1039
  const cache = getMessageCache(client);
741
1040
  return stream((emit, end) => {
742
- const pump = (async () => {
1041
+ const messagePump = (async () => {
743
1042
  try {
744
- for await (const event of sub) {
1043
+ for await (const event of messageSub) {
745
1044
  if (event.message.isFromMe) {
746
1045
  continue;
747
1046
  }
@@ -749,14 +1048,30 @@ var clientStream = (client) => {
749
1048
  await emit(message);
750
1049
  }
751
1050
  }
752
- end();
753
1051
  } catch (e) {
754
1052
  end(e);
755
1053
  }
756
1054
  })();
1055
+ const pollPump = (async () => {
1056
+ try {
1057
+ for await (const event of pollSub) {
1058
+ cachePollEvent(pollCache, event);
1059
+ if (event.actor.isFromMe) {
1060
+ continue;
1061
+ }
1062
+ const messages3 = await toPollDeltaMessages(client, pollCache, event);
1063
+ for (const vote of messages3) {
1064
+ await emit(vote);
1065
+ }
1066
+ }
1067
+ } catch (e) {
1068
+ console.error("[spectrum-ts][imessage][poll] stream failed", e);
1069
+ }
1070
+ })();
757
1071
  return async () => {
758
- sub.close();
759
- await pump;
1072
+ messageSub.close();
1073
+ pollSub.close();
1074
+ await Promise.all([messagePump, pollPump]);
760
1075
  };
761
1076
  });
762
1077
  };
@@ -774,7 +1089,10 @@ var sendContactAttachment = async (remote, content) => {
774
1089
  const upload = await sendVCardAttachment(remote, vcardFileName2(content), vcf);
775
1090
  return upload.guid;
776
1091
  };
777
- var messages2 = (clients) => mergeStreams(clients.map(clientStream));
1092
+ var messages2 = (clients) => {
1093
+ const pollCache = getPollCache(clients);
1094
+ return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
1095
+ };
778
1096
  var startTyping = async (clients, spaceId) => {
779
1097
  const remote = clients[0];
780
1098
  if (!remote) {
@@ -828,6 +1146,14 @@ var sendSingle = async (remote, chat, content) => {
828
1146
  })
829
1147
  );
830
1148
  }
1149
+ case "poll":
1150
+ return toSendResult(
1151
+ await remote.polls.create(
1152
+ chat,
1153
+ content.title,
1154
+ content.options.map((o) => o.title)
1155
+ )
1156
+ );
831
1157
  default:
832
1158
  throw unsupportedContent(content.type);
833
1159
  }
@@ -916,6 +1242,12 @@ var replyToMessage = async (clients, spaceId, msgId, content) => {
916
1242
  })
917
1243
  );
918
1244
  }
1245
+ case "poll":
1246
+ throw UnsupportedError.content(
1247
+ "poll",
1248
+ PLATFORM,
1249
+ "polls cannot be sent as replies"
1250
+ );
919
1251
  default:
920
1252
  throw unsupportedContent(content.type);
921
1253
  }
@@ -1,4 +1,4 @@
1
- import { d as Platform, c as PlatformDef, P as ProviderMessage, M as Message } from '../../types-D5KhSXLy.js';
1
+ import { d as Platform, c as PlatformDef, P as ProviderMessage, M as Message } from '../../types-DJQLFwWW.js';
2
2
  import z__default from 'zod';
3
3
  import 'hotscript';
4
4
 
@@ -120,6 +120,26 @@ declare const terminal: Platform<PlatformDef<"terminal", z__default.ZodObject<{
120
120
  } | {
121
121
  type: "group";
122
122
  items: Message[];
123
+ } | {
124
+ type: "poll";
125
+ title: string;
126
+ options: {
127
+ title: string;
128
+ }[];
129
+ } | {
130
+ type: "poll_option";
131
+ option: {
132
+ title: string;
133
+ };
134
+ poll: {
135
+ type: "poll";
136
+ title: string;
137
+ options: {
138
+ title: string;
139
+ }[];
140
+ };
141
+ selected: boolean;
142
+ title: string;
123
143
  };
124
144
  sender: {
125
145
  id: string;
@@ -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-D5KhSXLy.js';
5
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-DJQLFwWW.js';
6
6
  import * as zod_v4_core from 'zod/v4/core';
7
7
  import 'hotscript';
8
8
 
@@ -1,8 +1,9 @@
1
1
  import {
2
+ asPollOption,
2
3
  cloud,
3
4
  mergeStreams,
4
5
  stream
5
- } from "../../chunk-2Y5GBI6W.js";
6
+ } from "../../chunk-PXX7ISZ6.js";
6
7
  import {
7
8
  UnsupportedError,
8
9
  asAttachment,
@@ -233,6 +234,31 @@ var resubscribableStream = (state, options) => {
233
234
 
234
235
  // src/providers/whatsapp-business/messages.ts
235
236
  import { extension as mimeExtension } from "mime-types";
237
+
238
+ // src/providers/whatsapp-business/poll.ts
239
+ import {
240
+ button,
241
+ buttons,
242
+ list
243
+ } from "@photon-ai/whatsapp-business";
244
+ var MAX_BUTTON_OPTIONS = 3;
245
+ var LIST_BUTTON_TEXT = "View options";
246
+ var LIST_SECTION_TITLE = "Options";
247
+ var pollOptionId = (index) => `opt_${index}`;
248
+ var pollToInteractive = (content) => {
249
+ if (content.options.length <= MAX_BUTTON_OPTIONS) {
250
+ return buttons(
251
+ content.title,
252
+ ...content.options.map((o, i) => button(pollOptionId(i), o.title))
253
+ );
254
+ }
255
+ return list(content.title, LIST_BUTTON_TEXT).section(
256
+ LIST_SECTION_TITLE,
257
+ content.options.map((o, i) => ({ id: pollOptionId(i), title: o.title }))
258
+ );
259
+ };
260
+
261
+ // src/providers/whatsapp-business/messages.ts
236
262
  var primary = (clients) => {
237
263
  const client = clients[0];
238
264
  if (!client) {
@@ -243,6 +269,40 @@ var primary = (clients) => {
243
269
  var toSendResult = (result) => ({
244
270
  id: result.messageId
245
271
  });
272
+ var MAX_POLL_CACHE_SIZE = 1e3;
273
+ var OPTION_ID_PREFIX = "opt_";
274
+ var pollCaches = /* @__PURE__ */ new WeakMap();
275
+ var getPollCache = (client) => {
276
+ let cache = pollCaches.get(client);
277
+ if (!cache) {
278
+ cache = /* @__PURE__ */ new Map();
279
+ pollCaches.set(client, cache);
280
+ }
281
+ return cache;
282
+ };
283
+ var cachePoll = (client, messageId, poll) => {
284
+ const cache = getPollCache(client);
285
+ if (cache.has(messageId)) {
286
+ cache.delete(messageId);
287
+ }
288
+ cache.set(messageId, poll);
289
+ if (cache.size > MAX_POLL_CACHE_SIZE) {
290
+ const first = cache.keys().next().value;
291
+ if (first !== void 0) {
292
+ cache.delete(first);
293
+ }
294
+ }
295
+ };
296
+ var optionIndexFromId = (id) => {
297
+ if (!id.startsWith(OPTION_ID_PREFIX)) {
298
+ return;
299
+ }
300
+ const index = Number(id.slice(OPTION_ID_PREFIX.length));
301
+ if (!Number.isInteger(index) || index < 0 || pollOptionId(index) !== id) {
302
+ return;
303
+ }
304
+ return index;
305
+ };
246
306
  var mapWaPhoneType = (type) => {
247
307
  if (!type) {
248
308
  return void 0;
@@ -384,11 +444,12 @@ var toMessages = (client, msg) => {
384
444
  {
385
445
  ...base,
386
446
  id: msg.id,
387
- content: mapContent(client, msg.content)
447
+ content: mapContent(client, msg)
388
448
  }
389
449
  ];
390
450
  };
391
- var mapContent = (client, content) => {
451
+ var mapContent = (client, msg) => {
452
+ const { content } = msg;
392
453
  switch (content.type) {
393
454
  case "text":
394
455
  return asText(content.body);
@@ -411,8 +472,18 @@ var mapContent = (client, content) => {
411
472
  target: stubTarget
412
473
  });
413
474
  }
414
- case "interactive":
415
- return asCustom({ whatsapp_type: "interactive", ...content.interactive });
475
+ case "interactive": {
476
+ const inter = content.interactive;
477
+ if (inter.type === "button_reply" || inter.type === "list_reply") {
478
+ const poll = msg.context?.id === void 0 ? void 0 : getPollCache(client).get(msg.context.id);
479
+ const optionIndex = optionIndexFromId(inter.reply.id);
480
+ const option = optionIndex === void 0 ? void 0 : poll?.options[optionIndex];
481
+ if (poll && option) {
482
+ return asPollOption({ poll, option, selected: true });
483
+ }
484
+ }
485
+ return asCustom({ whatsapp_type: "interactive", ...inter });
486
+ }
416
487
  case "button":
417
488
  return asCustom({ whatsapp_type: "button", ...content.button });
418
489
  case "order":
@@ -589,6 +660,14 @@ var send = async (clients, spaceId, content) => {
589
660
  })
590
661
  );
591
662
  }
663
+ case "poll": {
664
+ const result = await client.messages.send({
665
+ to: spaceId,
666
+ interactive: pollToInteractive(content)
667
+ });
668
+ cachePoll(client, result.messageId, content);
669
+ return toSendResult(result);
670
+ }
592
671
  default:
593
672
  throw UnsupportedError.content(content.type);
594
673
  }
@@ -648,6 +727,15 @@ var replyToMessage = async (clients, spaceId, messageId, content) => {
648
727
  })
649
728
  );
650
729
  }
730
+ case "poll": {
731
+ const result = await client.messages.send({
732
+ to: spaceId,
733
+ replyTo: messageId,
734
+ interactive: pollToInteractive(content)
735
+ });
736
+ cachePoll(client, result.messageId, content);
737
+ return toSendResult(result);
738
+ }
651
739
  default:
652
740
  throw UnsupportedError.content(content.type);
653
741
  }
@@ -95,6 +95,26 @@ declare const contentSchema: z__default.ZodDiscriminatedUnion<[z__default.ZodObj
95
95
  }, z__default.core.$strip>, z__default.ZodObject<{
96
96
  type: z__default.ZodLiteral<"group">;
97
97
  items: z__default.ZodArray<z__default.ZodCustom<Message, Message>>;
98
+ }, z__default.core.$strip>, z__default.ZodObject<{
99
+ type: z__default.ZodLiteral<"poll">;
100
+ title: z__default.ZodString;
101
+ options: z__default.ZodArray<z__default.ZodObject<{
102
+ title: z__default.ZodString;
103
+ }, z__default.core.$strip>>;
104
+ }, z__default.core.$strip>, z__default.ZodObject<{
105
+ type: z__default.ZodLiteral<"poll_option">;
106
+ option: z__default.ZodObject<{
107
+ title: z__default.ZodString;
108
+ }, z__default.core.$strip>;
109
+ poll: z__default.ZodObject<{
110
+ type: z__default.ZodLiteral<"poll">;
111
+ title: z__default.ZodString;
112
+ options: z__default.ZodArray<z__default.ZodObject<{
113
+ title: z__default.ZodString;
114
+ }, z__default.core.$strip>>;
115
+ }, z__default.core.$strip>;
116
+ selected: z__default.ZodBoolean;
117
+ title: z__default.ZodString;
98
118
  }, z__default.core.$strip>], "type">;
99
119
  type Content = z__default.infer<typeof contentSchema>;
100
120
  interface ContentBuilder {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrum-ts",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@photon-ai/advanced-imessage": "^0.4.3",
22
+ "@photon-ai/advanced-imessage": "^0.5.1",
23
23
  "@photon-ai/imessage-kit": "^3.0.0",
24
24
  "@photon-ai/whatsapp-business": "^0.1.1",
25
25
  "@repeaterjs/repeater": "^3.0.6",