spectrum-ts 1.9.2 → 1.11.3

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.
@@ -93,458 +93,11 @@ function attachment(input, options) {
93
93
  };
94
94
  }
95
95
 
96
- // src/utils/vcard.ts
97
- import vCard from "vcf";
98
- var asPropertyArray = (prop) => {
99
- if (!prop) {
100
- return [];
101
- }
102
- const arr = Array.isArray(prop) ? prop : [prop];
103
- return arr;
104
- };
105
- var propString = (prop) => {
106
- const [first] = asPropertyArray(prop);
107
- const value = first?.valueOf().trim();
108
- return value ? value : void 0;
109
- };
110
- var paramTypes = (prop) => {
111
- const { type } = prop;
112
- if (!type) {
113
- return [];
114
- }
115
- return (Array.isArray(type) ? type : [type]).map((t) => t.toLowerCase());
116
- };
117
- var mapPhoneType = (prop) => {
118
- const types = paramTypes(prop);
119
- if (types.some((t) => t === "cell" || t === "mobile" || t === "iphone")) {
120
- return "mobile";
121
- }
122
- if (types.includes("home")) {
123
- return "home";
124
- }
125
- if (types.includes("work")) {
126
- return "work";
127
- }
128
- if (types.length > 0) {
129
- return "other";
130
- }
131
- return;
132
- };
133
- var mapSimpleType = (prop) => {
134
- const types = paramTypes(prop);
135
- if (types.includes("home")) {
136
- return "home";
137
- }
138
- if (types.includes("work")) {
139
- return "work";
140
- }
141
- if (types.length > 0) {
142
- return "other";
143
- }
144
- return;
145
- };
146
- var splitStructured = (value) => value.split(";").map((part) => part.trim());
147
- var extractName = (card) => {
148
- const fn = propString(card.data.fn);
149
- const n = propString(card.data.n);
150
- if (!(fn || n)) {
151
- return;
152
- }
153
- const result = {};
154
- if (fn) {
155
- result.formatted = fn;
156
- }
157
- if (n) {
158
- const [last, first, middle, prefix, suffix] = splitStructured(n);
159
- if (first) {
160
- result.first = first;
161
- }
162
- if (last) {
163
- result.last = last;
164
- }
165
- if (middle) {
166
- result.middle = middle;
167
- }
168
- if (prefix) {
169
- result.prefix = prefix;
170
- }
171
- if (suffix) {
172
- result.suffix = suffix;
173
- }
174
- }
175
- return result;
176
- };
177
- var extractPhones = (card) => {
178
- const props = asPropertyArray(card.data.tel);
179
- if (props.length === 0) {
180
- return;
181
- }
182
- return props.map((p) => {
183
- const entry = { value: p.valueOf().trim() };
184
- const type = mapPhoneType(p);
185
- if (type) {
186
- entry.type = type;
187
- }
188
- return entry;
189
- });
190
- };
191
- var extractEmails = (card) => {
192
- const props = asPropertyArray(card.data.email);
193
- if (props.length === 0) {
194
- return;
195
- }
196
- return props.map((p) => {
197
- const entry = { value: p.valueOf().trim() };
198
- const type = mapSimpleType(p);
199
- if (type) {
200
- entry.type = type;
201
- }
202
- return entry;
203
- });
204
- };
205
- var extractAddresses = (card) => {
206
- const props = asPropertyArray(card.data.adr);
207
- if (props.length === 0) {
208
- return;
209
- }
210
- return props.map((p) => {
211
- const [, , street, city, region, postalCode, country] = splitStructured(
212
- p.valueOf()
213
- );
214
- const entry = {};
215
- if (street) {
216
- entry.street = street;
217
- }
218
- if (city) {
219
- entry.city = city;
220
- }
221
- if (region) {
222
- entry.region = region;
223
- }
224
- if (postalCode) {
225
- entry.postalCode = postalCode;
226
- }
227
- if (country) {
228
- entry.country = country;
229
- }
230
- const type = mapSimpleType(p);
231
- if (type) {
232
- entry.type = type;
233
- }
234
- return entry;
235
- });
236
- };
237
- var extractOrg = (card) => {
238
- const orgStr = propString(card.data.org);
239
- const title = propString(card.data.title);
240
- if (!(orgStr || title)) {
241
- return;
242
- }
243
- const result = {};
244
- if (orgStr) {
245
- const [name, department] = splitStructured(orgStr);
246
- if (name) {
247
- result.name = name;
248
- }
249
- if (department) {
250
- result.department = department;
251
- }
252
- }
253
- if (title) {
254
- result.title = title;
255
- }
256
- return result;
257
- };
258
- var extractUrls = (card) => {
259
- const props = asPropertyArray(card.data.url);
260
- if (props.length === 0) {
261
- return;
262
- }
263
- return props.map((p) => p.valueOf().trim());
264
- };
265
- var photoMimeFromType = (type) => {
266
- if (!type) {
267
- return "image/jpeg";
268
- }
269
- const lower = type.toLowerCase();
270
- if (lower.startsWith("image/")) {
271
- return lower;
272
- }
273
- return `image/${lower}`;
274
- };
275
- var DATA_URI_PATTERN = /^data:([^;,]+);base64,(.*)$/i;
276
- var extractPhoto = (card) => {
277
- const [prop] = asPropertyArray(card.data.photo);
278
- if (!prop) {
279
- return;
280
- }
281
- const value = prop.valueOf();
282
- const dataUriMatch = DATA_URI_PATTERN.exec(value);
283
- if (dataUriMatch) {
284
- const [, mimeType, base64] = dataUriMatch;
285
- const buf2 = Buffer.from(base64 ?? "", "base64");
286
- return {
287
- mimeType: mimeType ?? "image/jpeg",
288
- read: async () => buf2
289
- };
290
- }
291
- const type = Array.isArray(prop.type) ? prop.type[0] : prop.type;
292
- const buf = Buffer.from(value, "base64");
293
- return {
294
- mimeType: photoMimeFromType(type),
295
- read: async () => buf
296
- };
297
- };
298
- var normalizeVCardInput = (vcf) => {
299
- const withoutBom = vcf.charCodeAt(0) === 65279 ? vcf.slice(1) : vcf;
300
- return withoutBom.replace(/\r\n|\r|\n/g, "\r\n");
301
- };
302
- var fromVCard = (vcf) => {
303
- const [card] = vCard.parse(normalizeVCardInput(vcf));
304
- if (!card) {
305
- throw new Error("Invalid vCard: no cards parsed");
306
- }
307
- const input = { raw: vcf };
308
- const name = extractName(card);
309
- if (name) {
310
- input.name = name;
311
- }
312
- const phones = extractPhones(card);
313
- if (phones) {
314
- input.phones = phones;
315
- }
316
- const emails = extractEmails(card);
317
- if (emails) {
318
- input.emails = emails;
319
- }
320
- const addresses = extractAddresses(card);
321
- if (addresses) {
322
- input.addresses = addresses;
323
- }
324
- const org = extractOrg(card);
325
- if (org) {
326
- input.org = org;
327
- }
328
- const urls = extractUrls(card);
329
- if (urls) {
330
- input.urls = urls;
331
- }
332
- const birthday = propString(card.data.bday);
333
- if (birthday) {
334
- input.birthday = birthday;
335
- }
336
- const note = propString(card.data.note);
337
- if (note) {
338
- input.note = note;
339
- }
340
- const photo = extractPhoto(card);
341
- if (photo) {
342
- input.photo = photo;
343
- }
344
- return input;
345
- };
346
- var formattedNameFor = (name) => {
347
- if (name?.formatted) {
348
- return name.formatted;
349
- }
350
- const parts = [name?.first, name?.middle, name?.last].filter(
351
- (p) => Boolean(p)
352
- );
353
- if (parts.length > 0) {
354
- return parts.join(" ");
355
- }
356
- return "Unknown";
357
- };
358
- var phoneTypeParam = (type) => {
359
- if (type === "mobile") {
360
- return "CELL";
361
- }
362
- if (type === "home" || type === "work" || type === "other") {
363
- return type.toUpperCase();
364
- }
365
- return;
366
- };
367
- var simpleTypeParam = (type) => type ? type.toUpperCase() : void 0;
368
- var photoTypeParam = (mimeType) => {
369
- const sub = mimeType.split("/")[1] ?? "jpeg";
370
- return sub.toUpperCase();
371
- };
372
- var writeName = (card, name) => {
373
- card.set("fn", formattedNameFor(name));
374
- if (!name) {
375
- return;
376
- }
377
- if (name.first || name.last || name.middle || name.prefix || name.suffix) {
378
- card.set(
379
- "n",
380
- [
381
- name.last ?? "",
382
- name.first ?? "",
383
- name.middle ?? "",
384
- name.prefix ?? "",
385
- name.suffix ?? ""
386
- ].join(";")
387
- );
388
- }
389
- };
390
- var writePhones = (card, phones) => {
391
- for (const phone of phones ?? []) {
392
- const type = phoneTypeParam(phone.type);
393
- card.add("tel", phone.value, type ? { type } : void 0);
394
- }
395
- };
396
- var writeEmails = (card, emails) => {
397
- for (const email of emails ?? []) {
398
- const type = simpleTypeParam(email.type);
399
- card.add("email", email.value, type ? { type } : void 0);
400
- }
401
- };
402
- var writeAddresses = (card, addresses) => {
403
- for (const addr of addresses ?? []) {
404
- const value = [
405
- "",
406
- "",
407
- addr.street ?? "",
408
- addr.city ?? "",
409
- addr.region ?? "",
410
- addr.postalCode ?? "",
411
- addr.country ?? ""
412
- ].join(";");
413
- const type = simpleTypeParam(addr.type);
414
- card.add("adr", value, type ? { type } : void 0);
415
- }
416
- };
417
- var writeOrg = (card, org) => {
418
- if (!org) {
419
- return;
420
- }
421
- if (org.name || org.department) {
422
- card.set("org", [org.name ?? "", org.department ?? ""].join(";"));
423
- }
424
- if (org.title) {
425
- card.set("title", org.title);
426
- }
427
- };
428
- var writeUrls = (card, urls) => {
429
- for (const url of urls ?? []) {
430
- card.add("url", url);
431
- }
432
- };
433
- var writePhoto = async (card, photo) => {
434
- if (!photo) {
435
- return;
436
- }
437
- const buf = await photo.read();
438
- card.set("photo", buf.toString("base64"), {
439
- encoding: "b",
440
- type: photoTypeParam(photo.mimeType)
441
- });
442
- };
443
- var toVCard = async (contact2) => {
444
- if (typeof contact2.raw === "string" && contact2.raw.startsWith("BEGIN:VCARD")) {
445
- return contact2.raw;
446
- }
447
- const card = new vCard();
448
- writeName(card, contact2.name);
449
- writePhones(card, contact2.phones);
450
- writeEmails(card, contact2.emails);
451
- writeAddresses(card, contact2.addresses);
452
- writeOrg(card, contact2.org);
453
- writeUrls(card, contact2.urls);
454
- if (contact2.birthday) {
455
- card.set("bday", contact2.birthday);
456
- }
457
- if (contact2.note) {
458
- card.set("note", contact2.note);
459
- }
460
- await writePhoto(card, contact2.photo);
461
- return card.toString();
462
- };
463
-
464
- // src/content/contact.ts
465
- import vCard2 from "vcf";
466
- import z3 from "zod";
467
- var userRefSchema = z3.object({
468
- __platform: z3.string(),
469
- id: z3.string()
470
- });
471
- var nameSchema = z3.object({
472
- formatted: z3.string().optional(),
473
- first: z3.string().optional(),
474
- last: z3.string().optional(),
475
- middle: z3.string().optional(),
476
- prefix: z3.string().optional(),
477
- suffix: z3.string().optional()
478
- });
479
- var phoneTypeSchema = z3.enum(["mobile", "home", "work", "other"]);
480
- var emailTypeSchema = z3.enum(["home", "work", "other"]);
481
- var addressTypeSchema = z3.enum(["home", "work", "other"]);
482
- var phoneSchema = z3.object({
483
- value: z3.string(),
484
- type: phoneTypeSchema.optional()
485
- });
486
- var emailSchema = z3.object({
487
- value: z3.string(),
488
- type: emailTypeSchema.optional()
489
- });
490
- var addressSchema = z3.object({
491
- street: z3.string().optional(),
492
- city: z3.string().optional(),
493
- region: z3.string().optional(),
494
- postalCode: z3.string().optional(),
495
- country: z3.string().optional(),
496
- type: addressTypeSchema.optional()
497
- });
498
- var orgSchema = z3.object({
499
- name: z3.string().optional(),
500
- title: z3.string().optional(),
501
- department: z3.string().optional()
502
- });
503
- var photoSchema = z3.object({
504
- mimeType: z3.string(),
505
- read: readSchema
506
- });
507
- var contactSchema = z3.object({
508
- type: z3.literal("contact"),
509
- user: userRefSchema.optional(),
510
- name: nameSchema.optional(),
511
- phones: z3.array(phoneSchema).optional(),
512
- emails: z3.array(emailSchema).optional(),
513
- addresses: z3.array(addressSchema).optional(),
514
- org: orgSchema.optional(),
515
- urls: z3.array(z3.string()).optional(),
516
- birthday: z3.string().optional(),
517
- note: z3.string().optional(),
518
- photo: photoSchema.optional(),
519
- raw: z3.unknown().optional()
520
- });
521
- var asContact = (input) => contactSchema.parse({ type: "contact", ...input });
522
- var isUser = (value) => typeof value === "object" && value !== null && "__platform" in value && "id" in value && typeof value.__platform === "string" && typeof value.id === "string";
523
- function contact(input, details) {
524
- return {
525
- build: async () => {
526
- if (typeof input === "string") {
527
- return asContact(fromVCard(input));
528
- }
529
- if (input instanceof vCard2) {
530
- return asContact(fromVCard(input.toString()));
531
- }
532
- if (isUser(input)) {
533
- return asContact({
534
- user: { __platform: input.__platform, id: input.id },
535
- ...details
536
- });
537
- }
538
- return asContact(input);
539
- }
540
- };
541
- }
542
-
543
96
  // src/content/custom.ts
544
- import z4 from "zod";
545
- var customSchema = z4.object({
546
- type: z4.literal("custom"),
547
- raw: z4.unknown()
97
+ import z3 from "zod";
98
+ var customSchema = z3.object({
99
+ type: z3.literal("custom"),
100
+ raw: z3.unknown()
548
101
  });
549
102
  var asCustom = (raw) => customSchema.parse({ type: "custom", raw });
550
103
  function custom(raw) {
@@ -554,10 +107,10 @@ function custom(raw) {
554
107
  }
555
108
 
556
109
  // src/content/text.ts
557
- import z5 from "zod";
558
- var textSchema = z5.object({
559
- type: z5.literal("text"),
560
- text: z5.string().nonempty()
110
+ import z4 from "zod";
111
+ var textSchema = z4.object({
112
+ type: z4.literal("text"),
113
+ text: z4.string().nonempty()
561
114
  });
562
115
  var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
563
116
  function text(text2) {
@@ -572,15 +125,15 @@ var resolveContents = (items) => Promise.all(
572
125
  );
573
126
 
574
127
  // src/content/edit.ts
575
- import z6 from "zod";
128
+ import z5 from "zod";
576
129
  var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
577
130
  var isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
578
- var editSchema = z6.object({
579
- type: z6.literal("edit"),
580
- content: z6.custom(isContent, {
131
+ var editSchema = z5.object({
132
+ type: z5.literal("edit"),
133
+ content: z5.custom(isContent, {
581
134
  message: "edit content must be a Content value"
582
135
  }),
583
- target: z6.custom(isMessage, {
136
+ target: z5.custom(isMessage, {
584
137
  message: "edit target must be a Message"
585
138
  })
586
139
  });
@@ -588,6 +141,11 @@ var asEdit = (input) => editSchema.parse({ type: "edit", ...input });
588
141
  function edit(content, target) {
589
142
  return {
590
143
  build: async () => {
144
+ if (target.direction !== "outbound") {
145
+ throw new Error(
146
+ `edit() target must be an outbound message (got direction "${target.direction}", message id "${target.id}")`
147
+ );
148
+ }
591
149
  const [resolved] = await resolveContents([content]);
592
150
  if (!resolved) {
593
151
  throw new Error("edit() requires content");
@@ -601,12 +159,12 @@ function edit(content, target) {
601
159
  }
602
160
 
603
161
  // src/content/reaction.ts
604
- import z7 from "zod";
162
+ import z6 from "zod";
605
163
  var isMessage2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
606
- var reactionSchema = z7.object({
607
- type: z7.literal("reaction"),
608
- emoji: z7.string().min(1),
609
- target: z7.custom(isMessage2, {
164
+ var reactionSchema = z6.object({
165
+ type: z6.literal("reaction"),
166
+ emoji: z6.string().min(1),
167
+ target: z6.custom(isMessage2, {
610
168
  message: "reaction target must be a Message"
611
169
  })
612
170
  });
@@ -623,15 +181,15 @@ function reaction(emoji, target) {
623
181
  }
624
182
 
625
183
  // src/content/reply.ts
626
- import z8 from "zod";
184
+ import z7 from "zod";
627
185
  var isMessage3 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
628
186
  var isContent2 = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
629
- var replySchema = z8.object({
630
- type: z8.literal("reply"),
631
- content: z8.custom(isContent2, {
187
+ var replySchema = z7.object({
188
+ type: z7.literal("reply"),
189
+ content: z7.custom(isContent2, {
632
190
  message: "reply content must be a Content value"
633
191
  }),
634
- target: z8.custom(isMessage3, {
192
+ target: z7.custom(isMessage3, {
635
193
  message: "reply target must be a Message"
636
194
  })
637
195
  });
@@ -652,10 +210,10 @@ function reply(content, target) {
652
210
  }
653
211
 
654
212
  // src/content/typing.ts
655
- import z9 from "zod";
656
- var typingSchema = z9.object({
657
- type: z9.literal("typing"),
658
- state: z9.enum(["start", "stop"])
213
+ import z8 from "zod";
214
+ var typingSchema = z8.object({
215
+ type: z8.literal("typing"),
216
+ state: z8.enum(["start", "stop"])
659
217
  });
660
218
  function typing(state = "start") {
661
219
  return {
@@ -860,8 +418,20 @@ var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
860
418
  "stopTyping",
861
419
  "responding"
862
420
  ]);
863
- var warnReservedAction = (name, platform) => {
864
- const body = `[spectrum-ts] ${platform} declared space action "${name}" which collides with a reserved Space key; skipping.`;
421
+ var RESERVED_MESSAGE_KEYS = /* @__PURE__ */ new Set([
422
+ "content",
423
+ "direction",
424
+ "edit",
425
+ "id",
426
+ "platform",
427
+ "react",
428
+ "reply",
429
+ "sender",
430
+ "space",
431
+ "timestamp"
432
+ ]);
433
+ var warnReservedAction = (scope, name, platform) => {
434
+ const body = `[spectrum-ts] ${platform} declared ${scope} action "${name}" which collides with a reserved ${scope === "space" ? "Space" : "Message"} key; skipping.`;
865
435
  console.warn(
866
436
  supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
867
437
  );
@@ -1110,10 +680,12 @@ function buildSpace(params) {
1110
680
  if (declaredActions) {
1111
681
  for (const [name, factory] of Object.entries(declaredActions)) {
1112
682
  if (RESERVED_SPACE_KEYS.has(name)) {
1113
- warnReservedAction(name, definition.name);
683
+ warnReservedAction("space", name, definition.name);
1114
684
  continue;
1115
685
  }
1116
- platformActions[name] = (...args) => space.send(factory(...args));
686
+ platformActions[name] = async (...args) => {
687
+ await factory(space, ...args);
688
+ };
1117
689
  }
1118
690
  }
1119
691
  space = {
@@ -1146,10 +718,6 @@ function buildSpace(params) {
1146
718
  function buildMessage(params) {
1147
719
  const { definition, space } = params;
1148
720
  let self;
1149
- const react = async (emoji) => {
1150
- const target = requireBuiltMessage("react");
1151
- await space.send(reaction(emoji, target));
1152
- };
1153
721
  const requireBuiltMessage = (action) => {
1154
722
  if (!self) {
1155
723
  throw new Error(
@@ -1158,46 +726,68 @@ function buildMessage(params) {
1158
726
  }
1159
727
  return self;
1160
728
  };
729
+ const react = async (emoji) => {
730
+ const target = requireBuiltMessage("react");
731
+ await space.send(reaction(emoji, target));
732
+ };
1161
733
  async function reply2(...content) {
1162
734
  const target = requireBuiltMessage("reply");
1163
735
  const wrapped = content.map((c) => reply(c, target));
1164
736
  return space.send(...wrapped);
1165
737
  }
1166
- const senderWithPlatform = params.sender === void 0 ? void 0 : { ...params.sender, __platform: definition.name };
1167
- if (params.direction === "outbound") {
1168
- const outbound = {
1169
- ...params.extras,
1170
- id: params.id,
1171
- content: params.content,
1172
- direction: "outbound",
1173
- platform: definition.name,
1174
- react,
1175
- reply: reply2,
1176
- edit: async (newContent) => {
1177
- const target = requireBuiltMessage("edit");
1178
- await space.send(edit(newContent, target));
1179
- },
1180
- sender: senderWithPlatform,
1181
- space,
1182
- timestamp: params.timestamp
1183
- };
1184
- self = outbound;
1185
- return outbound;
738
+ const edit2 = async (newContent) => {
739
+ const target = requireBuiltMessage("edit");
740
+ if (target.direction !== "outbound") {
741
+ throw new Error(
742
+ `cannot edit message ${target.id}: only outbound messages can be edited (direction: "${target.direction}")`
743
+ );
744
+ }
745
+ await space.send(edit(newContent, target));
746
+ };
747
+ const buildSenderWithPlatform = () => {
748
+ if (params.sender === void 0) {
749
+ return;
750
+ }
751
+ if (params.direction === "outbound") {
752
+ return {
753
+ ...params.sender,
754
+ __platform: definition.name,
755
+ kind: "agent"
756
+ };
757
+ }
758
+ return { ...params.sender, __platform: definition.name };
759
+ };
760
+ const senderWithPlatform = buildSenderWithPlatform();
761
+ const messagePlatformActions = {};
762
+ const declaredMessageActions = definition.message?.actions;
763
+ if (declaredMessageActions) {
764
+ for (const [name, factory] of Object.entries(declaredMessageActions)) {
765
+ if (RESERVED_MESSAGE_KEYS.has(name)) {
766
+ warnReservedAction("message", name, definition.name);
767
+ continue;
768
+ }
769
+ messagePlatformActions[name] = async (...args) => {
770
+ const target = requireBuiltMessage(name);
771
+ await factory(target, ...args);
772
+ };
773
+ }
1186
774
  }
1187
- const inbound = {
775
+ const message = {
1188
776
  ...params.extras,
777
+ ...messagePlatformActions,
1189
778
  id: params.id,
1190
779
  content: params.content,
1191
- direction: "inbound",
780
+ direction: params.direction,
1192
781
  platform: definition.name,
1193
782
  react,
1194
783
  reply: reply2,
784
+ edit: edit2,
1195
785
  sender: senderWithPlatform,
1196
786
  space,
1197
787
  timestamp: params.timestamp
1198
788
  };
1199
- self = inbound;
1200
- return inbound;
789
+ self = message;
790
+ return message;
1201
791
  }
1202
792
 
1203
793
  // src/platform/define.ts
@@ -1435,10 +1025,6 @@ export {
1435
1025
  attachmentSchema,
1436
1026
  asAttachment,
1437
1027
  attachment,
1438
- fromVCard,
1439
- toVCard,
1440
- asContact,
1441
- contact,
1442
1028
  asCustom,
1443
1029
  custom,
1444
1030
  textSchema,