spectrum-ts 1.9.2 → 1.12.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.
@@ -93,458 +93,79 @@ 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;
96
+ // src/content/avatar.ts
97
+ import z4 from "zod";
98
+
99
+ // src/utils/photo-content.ts
100
+ import { readFile as readFile2 } from "fs/promises";
101
+ import { basename as basename2 } from "path";
102
+ import { lookup as lookupMimeType2 } from "mime-types";
103
+ import z3 from "zod";
104
+ var CLEAR_SENTINEL = "clear";
105
+ var photoActionSchema = z3.discriminatedUnion("kind", [
106
+ z3.object({
107
+ kind: z3.literal("set"),
108
+ read: readSchema,
109
+ mimeType: z3.string().nonempty()
110
+ }),
111
+ z3.object({ kind: z3.literal("clear") })
112
+ ]);
113
+ var resolveMimeType = (input, mimeType, contentLabel) => {
114
+ if (mimeType) {
115
+ return mimeType;
242
116
  }
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;
117
+ if (typeof input === "string") {
118
+ const resolved = lookupMimeType2(basename2(input));
119
+ if (resolved) {
120
+ return resolved;
251
121
  }
252
122
  }
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)
123
+ throw new Error(
124
+ `Unable to resolve MIME type for ${contentLabel}. Pass options.mimeType explicitly.`
352
125
  );
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
126
  };
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
- });
127
+ var cachedRead = (read) => {
128
+ let cached;
129
+ return () => {
130
+ cached ??= read().catch((err) => {
131
+ cached = void 0;
132
+ throw err;
133
+ });
134
+ return cached;
135
+ };
442
136
  };
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();
137
+ var buildPhotoAction = (input, options, contentLabel) => {
138
+ if (input === CLEAR_SENTINEL) {
139
+ return { kind: "clear" };
140
+ }
141
+ const mimeType = resolveMimeType(input, options?.mimeType, contentLabel);
142
+ let read;
143
+ if (typeof input === "string") {
144
+ read = cachedRead(() => readFile2(input));
145
+ } else {
146
+ const snapshot = Buffer.from(input);
147
+ read = cachedRead(async () => snapshot);
148
+ }
149
+ return { kind: "set", read, mimeType };
462
150
  };
463
151
 
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()
152
+ // src/content/avatar.ts
153
+ var avatarSchema = z4.object({
154
+ type: z4.literal("avatar"),
155
+ action: photoActionSchema
470
156
  });
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) {
157
+ function avatar(input, options) {
158
+ const action = buildPhotoAction(input, options, "avatar");
524
159
  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
- }
160
+ build: async () => avatarSchema.parse({ type: "avatar", action })
540
161
  };
541
162
  }
542
163
 
543
164
  // src/content/custom.ts
544
- import z4 from "zod";
545
- var customSchema = z4.object({
546
- type: z4.literal("custom"),
547
- raw: z4.unknown()
165
+ import z5 from "zod";
166
+ var customSchema = z5.object({
167
+ type: z5.literal("custom"),
168
+ raw: z5.unknown()
548
169
  });
549
170
  var asCustom = (raw) => customSchema.parse({ type: "custom", raw });
550
171
  function custom(raw) {
@@ -554,10 +175,10 @@ function custom(raw) {
554
175
  }
555
176
 
556
177
  // src/content/text.ts
557
- import z5 from "zod";
558
- var textSchema = z5.object({
559
- type: z5.literal("text"),
560
- text: z5.string().nonempty()
178
+ import z6 from "zod";
179
+ var textSchema = z6.object({
180
+ type: z6.literal("text"),
181
+ text: z6.string().nonempty()
561
182
  });
562
183
  var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
563
184
  function text(text2) {
@@ -572,15 +193,15 @@ var resolveContents = (items) => Promise.all(
572
193
  );
573
194
 
574
195
  // src/content/edit.ts
575
- import z6 from "zod";
196
+ import z7 from "zod";
576
197
  var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
577
198
  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, {
199
+ var editSchema = z7.object({
200
+ type: z7.literal("edit"),
201
+ content: z7.custom(isContent, {
581
202
  message: "edit content must be a Content value"
582
203
  }),
583
- target: z6.custom(isMessage, {
204
+ target: z7.custom(isMessage, {
584
205
  message: "edit target must be a Message"
585
206
  })
586
207
  });
@@ -588,11 +209,16 @@ var asEdit = (input) => editSchema.parse({ type: "edit", ...input });
588
209
  function edit(content, target) {
589
210
  return {
590
211
  build: async () => {
212
+ if (target.direction !== "outbound") {
213
+ throw new Error(
214
+ `edit() target must be an outbound message (got direction "${target.direction}", message id "${target.id}")`
215
+ );
216
+ }
591
217
  const [resolved] = await resolveContents([content]);
592
218
  if (!resolved) {
593
219
  throw new Error("edit() requires content");
594
220
  }
595
- if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing") {
221
+ if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar") {
596
222
  throw new Error(`edit() cannot wrap "${resolved.type}" content`);
597
223
  }
598
224
  return asEdit({ content: resolved, target });
@@ -601,12 +227,12 @@ function edit(content, target) {
601
227
  }
602
228
 
603
229
  // src/content/reaction.ts
604
- import z7 from "zod";
230
+ import z8 from "zod";
605
231
  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, {
232
+ var reactionSchema = z8.object({
233
+ type: z8.literal("reaction"),
234
+ emoji: z8.string().min(1),
235
+ target: z8.custom(isMessage2, {
610
236
  message: "reaction target must be a Message"
611
237
  })
612
238
  });
@@ -622,16 +248,28 @@ function reaction(emoji, target) {
622
248
  };
623
249
  }
624
250
 
251
+ // src/content/rename.ts
252
+ import z9 from "zod";
253
+ var renameSchema = z9.object({
254
+ type: z9.literal("rename"),
255
+ displayName: z9.string().min(1, "rename() displayName must be non-empty")
256
+ });
257
+ function rename(displayName) {
258
+ return {
259
+ build: async () => renameSchema.parse({ type: "rename", displayName })
260
+ };
261
+ }
262
+
625
263
  // src/content/reply.ts
626
- import z8 from "zod";
264
+ import z10 from "zod";
627
265
  var isMessage3 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
628
266
  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, {
267
+ var replySchema = z10.object({
268
+ type: z10.literal("reply"),
269
+ content: z10.custom(isContent2, {
632
270
  message: "reply content must be a Content value"
633
271
  }),
634
- target: z8.custom(isMessage3, {
272
+ target: z10.custom(isMessage3, {
635
273
  message: "reply target must be a Message"
636
274
  })
637
275
  });
@@ -643,7 +281,7 @@ function reply(content, target) {
643
281
  if (!resolved) {
644
282
  throw new Error("reply() requires content");
645
283
  }
646
- if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing") {
284
+ if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing" || resolved.type === "rename" || resolved.type === "avatar") {
647
285
  throw new Error(`reply() cannot wrap "${resolved.type}" content`);
648
286
  }
649
287
  return asReply({ content: resolved, target });
@@ -652,10 +290,10 @@ function reply(content, target) {
652
290
  }
653
291
 
654
292
  // 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"])
293
+ import z11 from "zod";
294
+ var typingSchema = z11.object({
295
+ type: z11.literal("typing"),
296
+ state: z11.enum(["start", "stop"])
659
297
  });
660
298
  function typing(state = "start") {
661
299
  return {
@@ -847,7 +485,9 @@ var supportsAnsiColor = () => {
847
485
  var FIRE_AND_FORGET_TYPES = /* @__PURE__ */ new Set([
848
486
  "reaction",
849
487
  "typing",
850
- "edit"
488
+ "edit",
489
+ "rename",
490
+ "avatar"
851
491
  ]);
852
492
  var isFireAndForget = (item) => FIRE_AND_FORGET_TYPES.has(item.type) || item.__fireAndForget === true;
853
493
  var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
@@ -856,12 +496,26 @@ var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
856
496
  "send",
857
497
  "edit",
858
498
  "getMessage",
499
+ "rename",
500
+ "avatar",
859
501
  "startTyping",
860
502
  "stopTyping",
861
503
  "responding"
862
504
  ]);
863
- var warnReservedAction = (name, platform) => {
864
- const body = `[spectrum-ts] ${platform} declared space action "${name}" which collides with a reserved Space key; skipping.`;
505
+ var RESERVED_MESSAGE_KEYS = /* @__PURE__ */ new Set([
506
+ "content",
507
+ "direction",
508
+ "edit",
509
+ "id",
510
+ "platform",
511
+ "react",
512
+ "reply",
513
+ "sender",
514
+ "space",
515
+ "timestamp"
516
+ ]);
517
+ var warnReservedAction = (scope, name, platform) => {
518
+ const body = `[spectrum-ts] ${platform} declared ${scope} action "${name}" which collides with a reserved ${scope === "space" ? "Space" : "Message"} key; skipping.`;
865
519
  console.warn(
866
520
  supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
867
521
  );
@@ -1110,10 +764,12 @@ function buildSpace(params) {
1110
764
  if (declaredActions) {
1111
765
  for (const [name, factory] of Object.entries(declaredActions)) {
1112
766
  if (RESERVED_SPACE_KEYS.has(name)) {
1113
- warnReservedAction(name, definition.name);
767
+ warnReservedAction("space", name, definition.name);
1114
768
  continue;
1115
769
  }
1116
- platformActions[name] = (...args) => space.send(factory(...args));
770
+ platformActions[name] = async (...args) => {
771
+ await factory(space, ...args);
772
+ };
1117
773
  }
1118
774
  }
1119
775
  space = {
@@ -1125,6 +781,21 @@ function buildSpace(params) {
1125
781
  await space.send(edit(newContent, message));
1126
782
  },
1127
783
  getMessage: getMessageImpl,
784
+ rename: async (displayName) => {
785
+ await space.send(rename(displayName));
786
+ },
787
+ avatar: (async (input, options) => {
788
+ if (typeof input === "string") {
789
+ await space.send(avatar(input, options));
790
+ return;
791
+ }
792
+ if (!options?.mimeType) {
793
+ throw new Error(
794
+ "space.avatar(Buffer) requires options.mimeType \u2014 pass { mimeType: '...' }"
795
+ );
796
+ }
797
+ await space.send(avatar(input, { mimeType: options.mimeType }));
798
+ }),
1128
799
  startTyping: async () => {
1129
800
  await space.send(typing("start"));
1130
801
  },
@@ -1146,10 +817,6 @@ function buildSpace(params) {
1146
817
  function buildMessage(params) {
1147
818
  const { definition, space } = params;
1148
819
  let self;
1149
- const react = async (emoji) => {
1150
- const target = requireBuiltMessage("react");
1151
- await space.send(reaction(emoji, target));
1152
- };
1153
820
  const requireBuiltMessage = (action) => {
1154
821
  if (!self) {
1155
822
  throw new Error(
@@ -1158,46 +825,68 @@ function buildMessage(params) {
1158
825
  }
1159
826
  return self;
1160
827
  };
828
+ const react = async (emoji) => {
829
+ const target = requireBuiltMessage("react");
830
+ await space.send(reaction(emoji, target));
831
+ };
1161
832
  async function reply2(...content) {
1162
833
  const target = requireBuiltMessage("reply");
1163
834
  const wrapped = content.map((c) => reply(c, target));
1164
835
  return space.send(...wrapped);
1165
836
  }
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;
837
+ const edit2 = async (newContent) => {
838
+ const target = requireBuiltMessage("edit");
839
+ if (target.direction !== "outbound") {
840
+ throw new Error(
841
+ `cannot edit message ${target.id}: only outbound messages can be edited (direction: "${target.direction}")`
842
+ );
843
+ }
844
+ await space.send(edit(newContent, target));
845
+ };
846
+ const buildSenderWithPlatform = () => {
847
+ if (params.sender === void 0) {
848
+ return;
849
+ }
850
+ if (params.direction === "outbound") {
851
+ return {
852
+ ...params.sender,
853
+ __platform: definition.name,
854
+ kind: "agent"
855
+ };
856
+ }
857
+ return { ...params.sender, __platform: definition.name };
858
+ };
859
+ const senderWithPlatform = buildSenderWithPlatform();
860
+ const messagePlatformActions = {};
861
+ const declaredMessageActions = definition.message?.actions;
862
+ if (declaredMessageActions) {
863
+ for (const [name, factory] of Object.entries(declaredMessageActions)) {
864
+ if (RESERVED_MESSAGE_KEYS.has(name)) {
865
+ warnReservedAction("message", name, definition.name);
866
+ continue;
867
+ }
868
+ messagePlatformActions[name] = async (...args) => {
869
+ const target = requireBuiltMessage(name);
870
+ await factory(target, ...args);
871
+ };
872
+ }
1186
873
  }
1187
- const inbound = {
874
+ const message = {
1188
875
  ...params.extras,
876
+ ...messagePlatformActions,
1189
877
  id: params.id,
1190
878
  content: params.content,
1191
- direction: "inbound",
879
+ direction: params.direction,
1192
880
  platform: definition.name,
1193
881
  react,
1194
882
  reply: reply2,
883
+ edit: edit2,
1195
884
  sender: senderWithPlatform,
1196
885
  space,
1197
886
  timestamp: params.timestamp
1198
887
  };
1199
- self = inbound;
1200
- return inbound;
888
+ self = message;
889
+ return message;
1201
890
  }
1202
891
 
1203
892
  // src/platform/define.ts
@@ -1435,10 +1124,9 @@ export {
1435
1124
  attachmentSchema,
1436
1125
  asAttachment,
1437
1126
  attachment,
1438
- fromVCard,
1439
- toVCard,
1440
- asContact,
1441
- contact,
1127
+ photoActionSchema,
1128
+ buildPhotoAction,
1129
+ avatar,
1442
1130
  asCustom,
1443
1131
  custom,
1444
1132
  textSchema,
@@ -1449,6 +1137,7 @@ export {
1449
1137
  reactionSchema,
1450
1138
  asReaction,
1451
1139
  reaction,
1140
+ rename,
1452
1141
  reply,
1453
1142
  typing,
1454
1143
  UnsupportedError,