spectrum-ts 0.7.0 → 0.9.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,9 +1,9 @@
1
- import { M as ManagedStream } from '../../stream-DGy4geUK.js';
1
+ import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
2
  import { AdvancedIMessage } from '@photon-ai/advanced-imessage';
3
3
  import { IMessageSDK } from '@photon-ai/imessage-kit';
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-B5tTx5hc.js';
6
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.js';
7
7
  import * as zod_v4_core from 'zod/v4/core';
8
8
  import 'hotscript';
9
9
 
@@ -67,15 +67,6 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
67
67
  }[] | undefined;
68
68
  };
69
69
  }) => ManagedStream<IMessageMessage>;
70
- }>> & Readonly<{
71
- tapbacks: {
72
- readonly love: "love";
73
- readonly like: "like";
74
- readonly dislike: "dislike";
75
- readonly laugh: "laugh";
76
- readonly emphasize: "emphasize";
77
- readonly question: "question";
78
- };
79
- }>;
70
+ }>> & Readonly<Record<never, never>>;
80
71
 
81
72
  export { imessage };
@@ -1,18 +1,22 @@
1
+ import {
2
+ asRichlink
3
+ } from "../../chunk-6ZOLTQDN.js";
1
4
  import {
2
5
  asAttachment,
3
6
  asContact,
4
7
  asCustom,
8
+ asReaction,
5
9
  cloud,
6
10
  fromVCard,
7
11
  mergeStreams,
8
12
  stream,
9
13
  toVCard
10
- } from "../../chunk-GX3JCGSD.js";
14
+ } from "../../chunk-CZIWNTXP.js";
11
15
  import {
12
16
  UnsupportedError,
13
17
  asText,
14
18
  definePlatform
15
- } from "../../chunk-6URE4AYH.js";
19
+ } from "../../chunk-PLJI5FTO.js";
16
20
 
17
21
  // src/providers/imessage/index.ts
18
22
  import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
@@ -191,9 +195,9 @@ var messages = (client) => stream((emit, end) => {
191
195
  let lastPromise = Promise.resolve();
192
196
  const startPromise = client.startWatching({
193
197
  onIncomingMessage: (message) => {
194
- lastPromise = lastPromise.then(() => toMessages(message)).then((ms) => {
198
+ lastPromise = lastPromise.then(() => toMessages(message)).then(async (ms) => {
195
199
  for (const m of ms) {
196
- emit(m);
200
+ await emit(m);
197
201
  }
198
202
  }).catch(end);
199
203
  },
@@ -378,6 +382,7 @@ var ensureM4a = async (buffer, mimeType) => {
378
382
 
379
383
  // src/providers/imessage/remote.ts
380
384
  var PLATFORM = "iMessage";
385
+ var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
381
386
  var unsupportedContent = (type) => UnsupportedError.content(type, PLATFORM);
382
387
  var toSendResult = (receipt) => ({
383
388
  id: receipt.guid,
@@ -396,9 +401,47 @@ var isVCardAttachment2 = (mimeType, fileName) => {
396
401
  }
397
402
  return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
398
403
  };
399
- var TAPBACK_NAMES = new Set(
400
- Object.values(Reaction).filter((r) => r !== "emoji" && r !== "sticker")
404
+ var EMOJI_TO_TAPBACK = {
405
+ "\u2764\uFE0F": Reaction.love,
406
+ "\u{1F44D}": Reaction.like,
407
+ "\u{1F44E}": Reaction.dislike,
408
+ "\u{1F602}": Reaction.laugh,
409
+ "\u203C\uFE0F": Reaction.emphasize,
410
+ "\u2753": Reaction.question
411
+ };
412
+ var TAPBACK_TO_EMOJI = Object.fromEntries(
413
+ Object.entries(EMOJI_TO_TAPBACK).map(([emoji, kind]) => [kind, emoji])
401
414
  );
415
+ var TAPBACK_CODE_TO_KIND = {
416
+ "2000": Reaction.love,
417
+ "2001": Reaction.like,
418
+ "2002": Reaction.dislike,
419
+ "2003": Reaction.laugh,
420
+ "2004": Reaction.emphasize,
421
+ "2005": Reaction.question,
422
+ "2006": Reaction.emoji,
423
+ "2007": Reaction.sticker
424
+ };
425
+ var isTapbackRemoval = (code) => code.startsWith("3");
426
+ var resolveReactionEmoji = (type, emoji) => {
427
+ if (emoji) {
428
+ return emoji;
429
+ }
430
+ if (!type) {
431
+ return null;
432
+ }
433
+ const kind = TAPBACK_CODE_TO_KIND[type] ?? type;
434
+ return TAPBACK_TO_EMOJI[kind] ?? null;
435
+ };
436
+ var getAssociatedMessageType = (message) => {
437
+ const direct = message.associatedMessageType;
438
+ if (typeof direct === "string") {
439
+ return direct;
440
+ }
441
+ const raw = message._raw;
442
+ const fromRaw = raw?.associatedMessageType;
443
+ return typeof fromRaw === "string" ? fromRaw : void 0;
444
+ };
402
445
  var baseMessage = (event) => ({
403
446
  sender: { id: event.message.sender?.address ?? "" },
404
447
  space: {
@@ -422,9 +465,51 @@ var toVCardContent2 = async (client, info) => {
422
465
  return toAttachmentContent2(client, info);
423
466
  }
424
467
  };
468
+ var getBalloonBundleId = (message) => {
469
+ const raw = message._raw;
470
+ const id = raw?.balloonBundleId;
471
+ return typeof id === "string" ? id : void 0;
472
+ };
473
+ var toRichlinkMessage = (event, base, id) => {
474
+ const url = event.message.text ?? "";
475
+ try {
476
+ return { ...base, id, content: asRichlink({ url }) };
477
+ } catch {
478
+ return {
479
+ ...base,
480
+ id,
481
+ content: url ? asText(url) : asCustom(event.message)
482
+ };
483
+ }
484
+ };
485
+ var PART_PREFIX = /^p:\d+\//;
486
+ var toReactionMessage = (event, base, id, target) => {
487
+ const type = getAssociatedMessageType(event.message);
488
+ if (type && isTapbackRemoval(type)) {
489
+ return [];
490
+ }
491
+ const emoji = resolveReactionEmoji(
492
+ type,
493
+ event.message.associatedMessageEmoji
494
+ );
495
+ if (!emoji) {
496
+ return [];
497
+ }
498
+ const normalizedTarget = target.replace(PART_PREFIX, "");
499
+ return [
500
+ { ...base, id, content: asReaction({ emoji, target: normalizedTarget }) }
501
+ ];
502
+ };
425
503
  var toMessages2 = async (client, event) => {
426
504
  const base = baseMessage(event);
427
505
  const messageGuidStr = event.message.guid;
506
+ const assoc = event.message.associatedMessageGuid;
507
+ if (assoc) {
508
+ return toReactionMessage(event, base, messageGuidStr, assoc);
509
+ }
510
+ if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
511
+ return [toRichlinkMessage(event, base, messageGuidStr)];
512
+ }
428
513
  if (event.message.attachments.length > 0) {
429
514
  return Promise.all(
430
515
  event.message.attachments.map(async (info) => ({
@@ -446,14 +531,14 @@ var toMessages2 = async (client, event) => {
446
531
  var clientStream = (client) => {
447
532
  const sub = client.messages.subscribe("message.received");
448
533
  return stream((emit, end) => {
449
- (async () => {
534
+ const pump = (async () => {
450
535
  try {
451
536
  for await (const event of sub) {
452
537
  if (event.message.isFromMe) {
453
538
  continue;
454
539
  }
455
540
  for (const message of await toMessages2(client, event)) {
456
- emit(message);
541
+ await emit(message);
457
542
  }
458
543
  }
459
544
  end();
@@ -461,7 +546,10 @@ var clientStream = (client) => {
461
546
  end(e);
462
547
  }
463
548
  })();
464
- return () => sub.close();
549
+ return async () => {
550
+ sub.close();
551
+ await pump;
552
+ };
465
553
  });
466
554
  };
467
555
  var sendVCardAttachment = (remote, name, vcf) => remote.attachments.upload({
@@ -502,6 +590,10 @@ var send2 = async (clients, spaceId, content) => {
502
590
  switch (content.type) {
503
591
  case "text":
504
592
  return toSendResult(await remote.messages.send(chat, content.text));
593
+ case "richlink":
594
+ return toSendResult(
595
+ await remote.messages.send(chat, content.url, { richLink: true })
596
+ );
505
597
  case "attachment": {
506
598
  const attachment = await remote.attachments.upload({
507
599
  data: await content.read(),
@@ -551,6 +643,13 @@ var replyToMessage = async (clients, spaceId, msgId, content) => {
551
643
  return toSendResult(
552
644
  await remote.messages.send(chat, content.text, { replyTo })
553
645
  );
646
+ case "richlink":
647
+ return toSendResult(
648
+ await remote.messages.send(chat, content.url, {
649
+ richLink: true,
650
+ replyTo
651
+ })
652
+ );
554
653
  case "attachment": {
555
654
  const attachment = await remote.attachments.upload({
556
655
  data: await content.read(),
@@ -617,8 +716,9 @@ var reactToMessage = async (clients, spaceId, msgId, reaction) => {
617
716
  }
618
717
  const chat = chatGuid(spaceId);
619
718
  const msg = messageGuid(msgId);
620
- if (TAPBACK_NAMES.has(reaction)) {
621
- await remote.messages.react(chat, msg, reaction);
719
+ const native = EMOJI_TO_TAPBACK[reaction];
720
+ if (native) {
721
+ await remote.messages.react(chat, msg, native);
622
722
  } else {
623
723
  await remote.messages.reactEmoji(chat, msg, reaction);
624
724
  }
@@ -645,16 +745,6 @@ var spaceSchema = z.object({
645
745
  // src/providers/imessage/index.ts
646
746
  var imessage = definePlatform("iMessage", {
647
747
  config: configSchema,
648
- static: {
649
- tapbacks: {
650
- love: "love",
651
- like: "like",
652
- dislike: "dislike",
653
- laugh: "laugh",
654
- emphasize: "emphasize",
655
- question: "question"
656
- }
657
- },
658
748
  user: {
659
749
  resolve: async ({ input }) => ({ id: input.userID })
660
750
  },
@@ -741,7 +831,7 @@ var imessage = definePlatform("iMessage", {
741
831
  },
742
832
  reactToMessage: async ({ space, messageId, reaction, client }) => {
743
833
  if (isLocal(client)) {
744
- return;
834
+ throw UnsupportedError.action("react", "iMessage (local mode)");
745
835
  }
746
836
  await reactToMessage(client, space.id, messageId, reaction);
747
837
  },
@@ -1,4 +1,4 @@
1
- import { d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B5tTx5hc.js';
1
+ import { d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.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-6URE4AYH.js";
4
+ } from "../../chunk-PLJI5FTO.js";
5
5
 
6
6
  // src/providers/terminal/index.ts
7
7
  import { createInterface } from "readline";
@@ -1,8 +1,8 @@
1
- import { M as ManagedStream } from '../../stream-DGy4geUK.js';
1
+ 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-B5tTx5hc.js';
5
+ import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-B8g0pvfg.js';
6
6
  import * as zod_v4_core from 'zod/v4/core';
7
7
  import 'hotscript';
8
8
 
@@ -2,15 +2,16 @@ import {
2
2
  asAttachment,
3
3
  asContact,
4
4
  asCustom,
5
+ asReaction,
5
6
  cloud,
6
7
  mergeStreams,
7
8
  stream
8
- } from "../../chunk-GX3JCGSD.js";
9
+ } from "../../chunk-CZIWNTXP.js";
9
10
  import {
10
11
  UnsupportedError,
11
12
  asText,
12
13
  definePlatform
13
- } from "../../chunk-6URE4AYH.js";
14
+ } from "../../chunk-PLJI5FTO.js";
14
15
 
15
16
  // src/providers/whatsapp-business/index.ts
16
17
  import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
@@ -174,7 +175,7 @@ var pumpOnce = async (ctx) => {
174
175
  ctx.setActive(sub);
175
176
  try {
176
177
  for await (const event of sub) {
177
- ctx.emit(event);
178
+ await ctx.emit(event);
178
179
  }
179
180
  return true;
180
181
  } catch {
@@ -195,7 +196,7 @@ var resubscribableStream = (state, options) => {
195
196
  active = s;
196
197
  }
197
198
  };
198
- (async () => {
199
+ const pump = (async () => {
199
200
  while (!closed) {
200
201
  await pumpOnce(ctx);
201
202
  if (!closed) {
@@ -204,11 +205,12 @@ var resubscribableStream = (state, options) => {
204
205
  }
205
206
  end();
206
207
  })();
207
- return () => {
208
+ return async () => {
208
209
  closed = true;
209
210
  active?.close().catch(() => void 0);
210
211
  active = void 0;
211
212
  state.subscriptions.delete(subscription);
213
+ await pump;
212
214
  };
213
215
  });
214
216
  const subscription = {
@@ -400,7 +402,10 @@ var mapContent = (client, content) => {
400
402
  case "location":
401
403
  return asCustom({ whatsapp_type: "location", ...content.location });
402
404
  case "reaction":
403
- return asCustom({ whatsapp_type: "reaction", ...content.reaction });
405
+ return asReaction({
406
+ emoji: content.reaction.emoji,
407
+ target: content.reaction.messageId
408
+ });
404
409
  case "interactive":
405
410
  return asCustom({ whatsapp_type: "interactive", ...content.interactive });
406
411
  case "button":
@@ -518,11 +523,11 @@ var clientStream = (client) => {
518
523
  (e) => e.type === "message"
519
524
  );
520
525
  return stream((emit, end) => {
521
- (async () => {
526
+ const pump = (async () => {
522
527
  try {
523
528
  for await (const event of eventStream) {
524
529
  for (const m of toMessages(client, event.message)) {
525
- emit(m);
530
+ await emit(m);
526
531
  }
527
532
  }
528
533
  end();
@@ -530,7 +535,10 @@ var clientStream = (client) => {
530
535
  end(e);
531
536
  }
532
537
  })();
533
- return () => eventStream.close();
538
+ return async () => {
539
+ await eventStream.close();
540
+ await pump;
541
+ };
534
542
  });
535
543
  };
536
544
  var messages = (clients) => mergeStreams(clients.map(clientStream));
@@ -2,7 +2,7 @@ interface ManagedStream<T> extends AsyncIterable<T> {
2
2
  close(): Promise<void>;
3
3
  }
4
4
  type StreamCleanup = void | (() => void | Promise<void>);
5
- declare function stream<T>(setup: (emit: (value: T) => void, end: (error?: unknown) => void) => StreamCleanup | Promise<StreamCleanup>): ManagedStream<T>;
5
+ declare function stream<T>(setup: (emit: (value: T) => Promise<void>, end: (error?: unknown) => void) => StreamCleanup | Promise<StreamCleanup>): ManagedStream<T>;
6
6
  declare function mergeStreams<T>(streams: readonly ManagedStream<T>[]): ManagedStream<T>;
7
7
 
8
8
  export { type ManagedStream as M, mergeStreams as m, stream as s };
@@ -78,6 +78,20 @@ declare const contentSchema: z__default.ZodDiscriminatedUnion<[z__default.ZodObj
78
78
  size: z__default.ZodOptional<z__default.ZodNumber>;
79
79
  read: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>>>;
80
80
  stream: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodCustom<ReadableStream<unknown>, ReadableStream<unknown>>>>;
81
+ }, z__default.core.$strip>, z__default.ZodObject<{
82
+ type: z__default.ZodLiteral<"richlink">;
83
+ url: z__default.ZodURL;
84
+ title: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodOptional<z__default.ZodString>>>;
85
+ summary: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodOptional<z__default.ZodString>>>;
86
+ cover: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodOptional<z__default.ZodObject<{
87
+ mimeType: z__default.ZodOptional<z__default.ZodString>;
88
+ read: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>>>;
89
+ stream: z__default.ZodFunction<z__default.ZodTuple<readonly [], null>, z__default.ZodPromise<z__default.ZodCustom<ReadableStream<unknown>, ReadableStream<unknown>>>>;
90
+ }, z__default.core.$strip>>>>;
91
+ }, z__default.core.$strip>, z__default.ZodObject<{
92
+ type: z__default.ZodLiteral<"reaction">;
93
+ emoji: z__default.ZodString;
94
+ target: z__default.ZodString;
81
95
  }, z__default.core.$strip>], "type">;
82
96
  type Content = z__default.infer<typeof contentSchema>;
83
97
  interface ContentBuilder {
@@ -310,12 +324,13 @@ type SpaceShapeOf<Def extends AnyPlatformDef> = [SchemaSpaceOf<Def>] extends [
310
324
  never
311
325
  ] ? ResolvedSpaceOf<Def> : SchemaSpaceOf<Def>;
312
326
  type SpaceParamsInputOf<Def extends AnyPlatformDef> = InputSchema<Def["space"]["params"]>;
327
+ type SpaceUserLike<Def extends AnyPlatformDef> = PlatformUser<Def> | string;
313
328
  type SpaceArrayArgs<Def extends AnyPlatformDef> = [
314
329
  SpaceParamsInputOf<Def>
315
- ] extends [never] ? [users: PlatformUser<Def>[]] : [users: PlatformUser<Def>[]] | [users: PlatformUser<Def>[], params: SpaceParamsInputOf<Def>] | [params: SpaceParamsInputOf<Def>];
330
+ ] extends [never] ? [users: SpaceUserLike<Def>[]] : [users: SpaceUserLike<Def>[]] | [users: SpaceUserLike<Def>[], params: SpaceParamsInputOf<Def>] | [params: SpaceParamsInputOf<Def>];
316
331
  type SpaceVarargArgs<Def extends AnyPlatformDef> = [
317
332
  SpaceParamsInputOf<Def>
318
- ] extends [never] ? PlatformUser<Def>[] : PlatformUser<Def>[] | [...PlatformUser<Def>[], SpaceParamsInputOf<Def>];
333
+ ] extends [never] ? SpaceUserLike<Def>[] : SpaceUserLike<Def>[] | [...SpaceUserLike<Def>[], SpaceParamsInputOf<Def>];
319
334
  type SpaceArgs<Def extends AnyPlatformDef> = SpaceArrayArgs<Def> | SpaceVarargArgs<Def>;
320
335
  type PlatformSpace<Def extends AnyPlatformDef> = Omit<SpaceShapeOf<Def>, keyof Space> & Space;
321
336
  type PlatformMessage<Def extends AnyPlatformDef> = Omit<SchemaInfer<Def["message"]>, keyof Message> & Message<Def["name"], PlatformUser<Def>, PlatformSpace<Def>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrum-ts",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -25,6 +25,7 @@
25
25
  "@repeaterjs/repeater": "^3.0.6",
26
26
  "better-grpc": "^0.3.2",
27
27
  "mime-types": "^3.0.1",
28
+ "open-graph-scraper": "^6.11.0",
28
29
  "type-fest": "^5.4.1",
29
30
  "vcf": "^2.1.2",
30
31
  "zod": "^4.2.1"