spectrum-ts 0.9.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,710 +0,0 @@
1
- // src/content/attachment.ts
2
- import { createReadStream } from "fs";
3
- import { readFile, stat } from "fs/promises";
4
- import { basename } from "path";
5
- import { Readable } from "stream";
6
- import { lookup as lookupMimeType } from "mime-types";
7
- import z2 from "zod";
8
-
9
- // src/utils/io.ts
10
- import z from "zod";
11
- var readSchema = z.function({
12
- input: [],
13
- output: z.promise(z.instanceof(Buffer))
14
- });
15
- var streamSchema = z.function({
16
- input: [],
17
- output: z.promise(z.instanceof(ReadableStream))
18
- });
19
- var bufferToStream = (buf) => new ReadableStream({
20
- start(controller) {
21
- controller.enqueue(buf);
22
- controller.close();
23
- }
24
- });
25
-
26
- // src/content/attachment.ts
27
- var DEFAULT_ATTACHMENT_NAME = "attachment";
28
- var attachmentSchema = z2.object({
29
- type: z2.literal("attachment"),
30
- name: z2.string().nonempty(),
31
- mimeType: z2.string().nonempty(),
32
- size: z2.number().int().nonnegative().optional(),
33
- read: readSchema,
34
- stream: streamSchema
35
- });
36
- var resolveAttachmentName = (input, name) => name || (typeof input === "string" ? basename(input) : DEFAULT_ATTACHMENT_NAME);
37
- var resolveAttachmentMimeType = (name, mimeType) => {
38
- if (mimeType) {
39
- return mimeType;
40
- }
41
- const resolvedMimeType = lookupMimeType(name);
42
- if (!resolvedMimeType) {
43
- throw new Error(
44
- `Unable to resolve MIME type for attachment "${name}". Pass options.mimeType explicitly.`
45
- );
46
- }
47
- return resolvedMimeType;
48
- };
49
- var asAttachment = (input) => {
50
- let cached;
51
- const read = () => {
52
- cached ??= input.read().catch((err) => {
53
- cached = void 0;
54
- throw err;
55
- });
56
- return cached;
57
- };
58
- const stream2 = input.stream ?? (async () => bufferToStream(await read()));
59
- return attachmentSchema.parse({
60
- type: "attachment",
61
- name: input.name,
62
- mimeType: input.mimeType,
63
- size: input.size,
64
- read,
65
- stream: stream2
66
- });
67
- };
68
- function attachment(input, options) {
69
- return {
70
- build: async () => {
71
- const name = resolveAttachmentName(input, options?.name);
72
- const mimeType = resolveAttachmentMimeType(name, options?.mimeType);
73
- if (typeof input === "string") {
74
- const stats = await stat(input);
75
- return asAttachment({
76
- name,
77
- mimeType,
78
- size: stats.size,
79
- read: () => readFile(input),
80
- stream: async () => Readable.toWeb(
81
- createReadStream(input)
82
- )
83
- });
84
- }
85
- return asAttachment({
86
- name,
87
- mimeType,
88
- size: input.byteLength,
89
- read: async () => input,
90
- stream: async () => bufferToStream(input)
91
- });
92
- }
93
- };
94
- }
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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 void 0;
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
- // src/content/custom.ts
544
- import z4 from "zod";
545
- var customSchema = z4.object({
546
- type: z4.literal("custom"),
547
- raw: z4.unknown()
548
- });
549
- var asCustom = (raw) => customSchema.parse({ type: "custom", raw });
550
- function custom(raw) {
551
- return {
552
- build: async () => asCustom(raw)
553
- };
554
- }
555
-
556
- // src/content/reaction.ts
557
- import z5 from "zod";
558
- var reactionSchema = z5.object({
559
- type: z5.literal("reaction"),
560
- emoji: z5.string().min(1),
561
- target: z5.string().min(1)
562
- });
563
- var asReaction = (input) => reactionSchema.parse({ type: "reaction", ...input });
564
- function reaction(emoji, target) {
565
- const targetId = typeof target === "string" ? target : target.id;
566
- return { build: async () => asReaction({ emoji, target: targetId }) };
567
- }
568
-
569
- // src/utils/stream.ts
570
- import { Repeater } from "@repeaterjs/repeater";
571
- function stream(setup) {
572
- const repeater = new Repeater(async (push, stop) => {
573
- const emit = async (value) => {
574
- try {
575
- await push(value);
576
- } catch (error) {
577
- stop(error);
578
- throw error;
579
- }
580
- };
581
- const end = (error) => {
582
- stop(error);
583
- };
584
- const cleanup = await setup(emit, end);
585
- try {
586
- await stop;
587
- } finally {
588
- await cleanup?.();
589
- }
590
- });
591
- return Object.assign(repeater, {
592
- close: async () => {
593
- await repeater.return(void 0);
594
- }
595
- });
596
- }
597
- function mergeStreams(streams) {
598
- return stream((emit, end) => {
599
- if (streams.length === 0) {
600
- end();
601
- return;
602
- }
603
- let openStreams = streams.length;
604
- const workers = streams.map(async (source) => {
605
- try {
606
- for await (const value of source) {
607
- await emit(value);
608
- }
609
- } catch (error) {
610
- end(error);
611
- } finally {
612
- openStreams -= 1;
613
- if (openStreams === 0) {
614
- end();
615
- }
616
- }
617
- });
618
- return async () => {
619
- await Promise.allSettled(streams.map((source) => source.close()));
620
- await Promise.allSettled(workers);
621
- };
622
- });
623
- }
624
-
625
- // src/utils/cloud.ts
626
- var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum.photon.codes"}`;
627
- var SpectrumCloudError = class extends Error {
628
- status;
629
- code;
630
- constructor(status, code, message) {
631
- super(message);
632
- this.name = "SpectrumCloudError";
633
- this.status = status;
634
- this.code = code;
635
- }
636
- };
637
- var request = async (path, init) => {
638
- const response = await fetch(`${SPECTRUM_CLOUD_URL}${path}`, init);
639
- if (!response.ok) {
640
- const body = await response.text().catch(() => "");
641
- try {
642
- const parsed = JSON.parse(body);
643
- throw new SpectrumCloudError(
644
- response.status,
645
- parsed.code,
646
- parsed.message
647
- );
648
- } catch (error) {
649
- if (error instanceof SpectrumCloudError) {
650
- throw error;
651
- }
652
- throw new SpectrumCloudError(
653
- response.status,
654
- "UNKNOWN",
655
- body || response.statusText
656
- );
657
- }
658
- }
659
- const json = await response.json();
660
- if (!json.succeed) {
661
- throw new SpectrumCloudError(
662
- response.status,
663
- "UNKNOWN",
664
- "Server returned succeed=false"
665
- );
666
- }
667
- return json.data;
668
- };
669
- var basicAuth = (projectId, projectSecret) => `Basic ${btoa(`${projectId}:${projectSecret}`)}`;
670
- var cloud = {
671
- getSubscription: (projectId) => request(`/projects/${projectId}/billing/subscription`),
672
- issueImessageTokens: (projectId, projectSecret) => request(`/projects/${projectId}/imessage/tokens`, {
673
- method: "POST",
674
- headers: { Authorization: basicAuth(projectId, projectSecret) }
675
- }),
676
- getImessageInfo: (projectId) => request(`/projects/${projectId}/imessage/`),
677
- issueWhatsappBusinessTokens: (projectId, projectSecret) => request(`/projects/${projectId}/whatsapp-business/tokens`, {
678
- method: "POST",
679
- headers: { Authorization: basicAuth(projectId, projectSecret) }
680
- }),
681
- getPlatforms: (projectId) => request(`/projects/${projectId}/platforms/`),
682
- togglePlatform: (projectId, projectSecret, platform, enabled) => request(`/projects/${projectId}/platforms/`, {
683
- method: "PATCH",
684
- headers: {
685
- Authorization: basicAuth(projectId, projectSecret),
686
- "Content-Type": "application/json"
687
- },
688
- body: JSON.stringify({ platform, enabled })
689
- })
690
- };
691
-
692
- export {
693
- readSchema,
694
- streamSchema,
695
- bufferToStream,
696
- asAttachment,
697
- attachment,
698
- fromVCard,
699
- toVCard,
700
- asContact,
701
- contact,
702
- asCustom,
703
- custom,
704
- asReaction,
705
- reaction,
706
- stream,
707
- mergeStreams,
708
- SpectrumCloudError,
709
- cloud
710
- };