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.
- package/dist/{chunk-XZSBR26X.js → chunk-3BTOWGQL.js} +96 -510
- package/dist/{chunk-N3ZKMTSG.js → chunk-AYB6DT76.js} +8 -6
- package/dist/{chunk-TN54TDTQ.js → chunk-EW5XWI3Y.js} +1 -1
- package/dist/{chunk-7O5BCLGQ.js → chunk-G7TWBZUE.js} +107 -35
- package/dist/chunk-KO67KDBD.js +61 -0
- package/dist/chunk-LCJMR75R.js +457 -0
- package/dist/{chunk-OR3VGVML.js → chunk-PUMFTTA4.js} +8 -4
- package/dist/chunk-T2TBVX6C.js +524 -0
- package/dist/{chunk-HWADNTQF.js → chunk-YKWKZ2PZ.js} +4 -59
- package/dist/{chunk-5NHNMN4H.js → chunk-YM4OAVPX.js} +1 -1
- package/dist/index.d.ts +41 -30
- package/dist/index.js +13 -9
- package/dist/providers/imessage/index.d.ts +30 -3
- package/dist/providers/imessage/index.js +10 -6
- package/dist/providers/index.d.ts +3 -1
- package/dist/providers/index.js +13 -7
- package/dist/providers/slack/index.d.ts +47 -0
- package/dist/providers/slack/index.js +8 -0
- package/dist/providers/terminal/index.d.ts +10 -10
- package/dist/providers/terminal/index.js +4 -3
- package/dist/providers/whatsapp-business/index.d.ts +2 -2
- package/dist/providers/whatsapp-business/index.js +5 -3
- package/dist/{types-lUyzRurY.d.ts → types-Dd3RC_60.d.ts} +93 -52
- package/package.json +12 -1
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readSchema
|
|
3
|
+
} from "./chunk-3BTOWGQL.js";
|
|
4
|
+
|
|
5
|
+
// src/utils/vcard.ts
|
|
6
|
+
import vCard from "vcf";
|
|
7
|
+
var asPropertyArray = (prop) => {
|
|
8
|
+
if (!prop) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const arr = Array.isArray(prop) ? prop : [prop];
|
|
12
|
+
return arr;
|
|
13
|
+
};
|
|
14
|
+
var propString = (prop) => {
|
|
15
|
+
const [first] = asPropertyArray(prop);
|
|
16
|
+
const value = first?.valueOf().trim();
|
|
17
|
+
return value ? value : void 0;
|
|
18
|
+
};
|
|
19
|
+
var paramTypes = (prop) => {
|
|
20
|
+
const { type } = prop;
|
|
21
|
+
if (!type) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return (Array.isArray(type) ? type : [type]).map((t) => t.toLowerCase());
|
|
25
|
+
};
|
|
26
|
+
var mapPhoneType = (prop) => {
|
|
27
|
+
const types = paramTypes(prop);
|
|
28
|
+
if (types.some((t) => t === "cell" || t === "mobile" || t === "iphone")) {
|
|
29
|
+
return "mobile";
|
|
30
|
+
}
|
|
31
|
+
if (types.includes("home")) {
|
|
32
|
+
return "home";
|
|
33
|
+
}
|
|
34
|
+
if (types.includes("work")) {
|
|
35
|
+
return "work";
|
|
36
|
+
}
|
|
37
|
+
if (types.length > 0) {
|
|
38
|
+
return "other";
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
};
|
|
42
|
+
var mapSimpleType = (prop) => {
|
|
43
|
+
const types = paramTypes(prop);
|
|
44
|
+
if (types.includes("home")) {
|
|
45
|
+
return "home";
|
|
46
|
+
}
|
|
47
|
+
if (types.includes("work")) {
|
|
48
|
+
return "work";
|
|
49
|
+
}
|
|
50
|
+
if (types.length > 0) {
|
|
51
|
+
return "other";
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
};
|
|
55
|
+
var splitStructured = (value) => value.split(";").map((part) => part.trim());
|
|
56
|
+
var extractName = (card) => {
|
|
57
|
+
const fn = propString(card.data.fn);
|
|
58
|
+
const n = propString(card.data.n);
|
|
59
|
+
if (!(fn || n)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const result = {};
|
|
63
|
+
if (fn) {
|
|
64
|
+
result.formatted = fn;
|
|
65
|
+
}
|
|
66
|
+
if (n) {
|
|
67
|
+
const [last, first, middle, prefix, suffix] = splitStructured(n);
|
|
68
|
+
if (first) {
|
|
69
|
+
result.first = first;
|
|
70
|
+
}
|
|
71
|
+
if (last) {
|
|
72
|
+
result.last = last;
|
|
73
|
+
}
|
|
74
|
+
if (middle) {
|
|
75
|
+
result.middle = middle;
|
|
76
|
+
}
|
|
77
|
+
if (prefix) {
|
|
78
|
+
result.prefix = prefix;
|
|
79
|
+
}
|
|
80
|
+
if (suffix) {
|
|
81
|
+
result.suffix = suffix;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
var extractPhones = (card) => {
|
|
87
|
+
const props = asPropertyArray(card.data.tel);
|
|
88
|
+
if (props.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
return props.map((p) => {
|
|
92
|
+
const entry = { value: p.valueOf().trim() };
|
|
93
|
+
const type = mapPhoneType(p);
|
|
94
|
+
if (type) {
|
|
95
|
+
entry.type = type;
|
|
96
|
+
}
|
|
97
|
+
return entry;
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
var extractEmails = (card) => {
|
|
101
|
+
const props = asPropertyArray(card.data.email);
|
|
102
|
+
if (props.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return props.map((p) => {
|
|
106
|
+
const entry = { value: p.valueOf().trim() };
|
|
107
|
+
const type = mapSimpleType(p);
|
|
108
|
+
if (type) {
|
|
109
|
+
entry.type = type;
|
|
110
|
+
}
|
|
111
|
+
return entry;
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
var extractAddresses = (card) => {
|
|
115
|
+
const props = asPropertyArray(card.data.adr);
|
|
116
|
+
if (props.length === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
return props.map((p) => {
|
|
120
|
+
const [, , street, city, region, postalCode, country] = splitStructured(
|
|
121
|
+
p.valueOf()
|
|
122
|
+
);
|
|
123
|
+
const entry = {};
|
|
124
|
+
if (street) {
|
|
125
|
+
entry.street = street;
|
|
126
|
+
}
|
|
127
|
+
if (city) {
|
|
128
|
+
entry.city = city;
|
|
129
|
+
}
|
|
130
|
+
if (region) {
|
|
131
|
+
entry.region = region;
|
|
132
|
+
}
|
|
133
|
+
if (postalCode) {
|
|
134
|
+
entry.postalCode = postalCode;
|
|
135
|
+
}
|
|
136
|
+
if (country) {
|
|
137
|
+
entry.country = country;
|
|
138
|
+
}
|
|
139
|
+
const type = mapSimpleType(p);
|
|
140
|
+
if (type) {
|
|
141
|
+
entry.type = type;
|
|
142
|
+
}
|
|
143
|
+
return entry;
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
var extractOrg = (card) => {
|
|
147
|
+
const orgStr = propString(card.data.org);
|
|
148
|
+
const title = propString(card.data.title);
|
|
149
|
+
if (!(orgStr || title)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const result = {};
|
|
153
|
+
if (orgStr) {
|
|
154
|
+
const [name, department] = splitStructured(orgStr);
|
|
155
|
+
if (name) {
|
|
156
|
+
result.name = name;
|
|
157
|
+
}
|
|
158
|
+
if (department) {
|
|
159
|
+
result.department = department;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (title) {
|
|
163
|
+
result.title = title;
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
var extractUrls = (card) => {
|
|
168
|
+
const props = asPropertyArray(card.data.url);
|
|
169
|
+
if (props.length === 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
return props.map((p) => p.valueOf().trim());
|
|
173
|
+
};
|
|
174
|
+
var photoMimeFromType = (type) => {
|
|
175
|
+
if (!type) {
|
|
176
|
+
return "image/jpeg";
|
|
177
|
+
}
|
|
178
|
+
const lower = type.toLowerCase();
|
|
179
|
+
if (lower.startsWith("image/")) {
|
|
180
|
+
return lower;
|
|
181
|
+
}
|
|
182
|
+
return `image/${lower}`;
|
|
183
|
+
};
|
|
184
|
+
var DATA_URI_PATTERN = /^data:([^;,]+);base64,(.*)$/i;
|
|
185
|
+
var extractPhoto = (card) => {
|
|
186
|
+
const [prop] = asPropertyArray(card.data.photo);
|
|
187
|
+
if (!prop) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const value = prop.valueOf();
|
|
191
|
+
const dataUriMatch = DATA_URI_PATTERN.exec(value);
|
|
192
|
+
if (dataUriMatch) {
|
|
193
|
+
const [, mimeType, base64] = dataUriMatch;
|
|
194
|
+
const buf2 = Buffer.from(base64 ?? "", "base64");
|
|
195
|
+
return {
|
|
196
|
+
mimeType: mimeType ?? "image/jpeg",
|
|
197
|
+
read: async () => buf2
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const type = Array.isArray(prop.type) ? prop.type[0] : prop.type;
|
|
201
|
+
const buf = Buffer.from(value, "base64");
|
|
202
|
+
return {
|
|
203
|
+
mimeType: photoMimeFromType(type),
|
|
204
|
+
read: async () => buf
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
var normalizeVCardInput = (vcf) => {
|
|
208
|
+
const withoutBom = vcf.charCodeAt(0) === 65279 ? vcf.slice(1) : vcf;
|
|
209
|
+
return withoutBom.replace(/\r\n|\r|\n/g, "\r\n");
|
|
210
|
+
};
|
|
211
|
+
var fromVCard = (vcf) => {
|
|
212
|
+
const [card] = vCard.parse(normalizeVCardInput(vcf));
|
|
213
|
+
if (!card) {
|
|
214
|
+
throw new Error("Invalid vCard: no cards parsed");
|
|
215
|
+
}
|
|
216
|
+
const input = { raw: vcf };
|
|
217
|
+
const name = extractName(card);
|
|
218
|
+
if (name) {
|
|
219
|
+
input.name = name;
|
|
220
|
+
}
|
|
221
|
+
const phones = extractPhones(card);
|
|
222
|
+
if (phones) {
|
|
223
|
+
input.phones = phones;
|
|
224
|
+
}
|
|
225
|
+
const emails = extractEmails(card);
|
|
226
|
+
if (emails) {
|
|
227
|
+
input.emails = emails;
|
|
228
|
+
}
|
|
229
|
+
const addresses = extractAddresses(card);
|
|
230
|
+
if (addresses) {
|
|
231
|
+
input.addresses = addresses;
|
|
232
|
+
}
|
|
233
|
+
const org = extractOrg(card);
|
|
234
|
+
if (org) {
|
|
235
|
+
input.org = org;
|
|
236
|
+
}
|
|
237
|
+
const urls = extractUrls(card);
|
|
238
|
+
if (urls) {
|
|
239
|
+
input.urls = urls;
|
|
240
|
+
}
|
|
241
|
+
const birthday = propString(card.data.bday);
|
|
242
|
+
if (birthday) {
|
|
243
|
+
input.birthday = birthday;
|
|
244
|
+
}
|
|
245
|
+
const note = propString(card.data.note);
|
|
246
|
+
if (note) {
|
|
247
|
+
input.note = note;
|
|
248
|
+
}
|
|
249
|
+
const photo = extractPhoto(card);
|
|
250
|
+
if (photo) {
|
|
251
|
+
input.photo = photo;
|
|
252
|
+
}
|
|
253
|
+
return input;
|
|
254
|
+
};
|
|
255
|
+
var formattedNameFor = (name) => {
|
|
256
|
+
if (name?.formatted) {
|
|
257
|
+
return name.formatted;
|
|
258
|
+
}
|
|
259
|
+
const parts = [name?.first, name?.middle, name?.last].filter(
|
|
260
|
+
(p) => Boolean(p)
|
|
261
|
+
);
|
|
262
|
+
if (parts.length > 0) {
|
|
263
|
+
return parts.join(" ");
|
|
264
|
+
}
|
|
265
|
+
return "Unknown";
|
|
266
|
+
};
|
|
267
|
+
var phoneTypeParam = (type) => {
|
|
268
|
+
if (type === "mobile") {
|
|
269
|
+
return "CELL";
|
|
270
|
+
}
|
|
271
|
+
if (type === "home" || type === "work" || type === "other") {
|
|
272
|
+
return type.toUpperCase();
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
};
|
|
276
|
+
var simpleTypeParam = (type) => type ? type.toUpperCase() : void 0;
|
|
277
|
+
var photoTypeParam = (mimeType) => {
|
|
278
|
+
const sub = mimeType.split("/")[1] ?? "jpeg";
|
|
279
|
+
return sub.toUpperCase();
|
|
280
|
+
};
|
|
281
|
+
var writeName = (card, name) => {
|
|
282
|
+
card.set("fn", formattedNameFor(name));
|
|
283
|
+
if (!name) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (name.first || name.last || name.middle || name.prefix || name.suffix) {
|
|
287
|
+
card.set(
|
|
288
|
+
"n",
|
|
289
|
+
[
|
|
290
|
+
name.last ?? "",
|
|
291
|
+
name.first ?? "",
|
|
292
|
+
name.middle ?? "",
|
|
293
|
+
name.prefix ?? "",
|
|
294
|
+
name.suffix ?? ""
|
|
295
|
+
].join(";")
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var writePhones = (card, phones) => {
|
|
300
|
+
for (const phone of phones ?? []) {
|
|
301
|
+
const type = phoneTypeParam(phone.type);
|
|
302
|
+
card.add("tel", phone.value, type ? { type } : void 0);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var writeEmails = (card, emails) => {
|
|
306
|
+
for (const email of emails ?? []) {
|
|
307
|
+
const type = simpleTypeParam(email.type);
|
|
308
|
+
card.add("email", email.value, type ? { type } : void 0);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
var writeAddresses = (card, addresses) => {
|
|
312
|
+
for (const addr of addresses ?? []) {
|
|
313
|
+
const value = [
|
|
314
|
+
"",
|
|
315
|
+
"",
|
|
316
|
+
addr.street ?? "",
|
|
317
|
+
addr.city ?? "",
|
|
318
|
+
addr.region ?? "",
|
|
319
|
+
addr.postalCode ?? "",
|
|
320
|
+
addr.country ?? ""
|
|
321
|
+
].join(";");
|
|
322
|
+
const type = simpleTypeParam(addr.type);
|
|
323
|
+
card.add("adr", value, type ? { type } : void 0);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var writeOrg = (card, org) => {
|
|
327
|
+
if (!org) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (org.name || org.department) {
|
|
331
|
+
card.set("org", [org.name ?? "", org.department ?? ""].join(";"));
|
|
332
|
+
}
|
|
333
|
+
if (org.title) {
|
|
334
|
+
card.set("title", org.title);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var writeUrls = (card, urls) => {
|
|
338
|
+
for (const url of urls ?? []) {
|
|
339
|
+
card.add("url", url);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var writePhoto = async (card, photo) => {
|
|
343
|
+
if (!photo) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const buf = await photo.read();
|
|
347
|
+
card.set("photo", buf.toString("base64"), {
|
|
348
|
+
encoding: "b",
|
|
349
|
+
type: photoTypeParam(photo.mimeType)
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
var toVCard = async (contact2) => {
|
|
353
|
+
if (typeof contact2.raw === "string" && contact2.raw.startsWith("BEGIN:VCARD")) {
|
|
354
|
+
return contact2.raw;
|
|
355
|
+
}
|
|
356
|
+
const card = new vCard();
|
|
357
|
+
writeName(card, contact2.name);
|
|
358
|
+
writePhones(card, contact2.phones);
|
|
359
|
+
writeEmails(card, contact2.emails);
|
|
360
|
+
writeAddresses(card, contact2.addresses);
|
|
361
|
+
writeOrg(card, contact2.org);
|
|
362
|
+
writeUrls(card, contact2.urls);
|
|
363
|
+
if (contact2.birthday) {
|
|
364
|
+
card.set("bday", contact2.birthday);
|
|
365
|
+
}
|
|
366
|
+
if (contact2.note) {
|
|
367
|
+
card.set("note", contact2.note);
|
|
368
|
+
}
|
|
369
|
+
await writePhoto(card, contact2.photo);
|
|
370
|
+
return card.toString();
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/content/contact.ts
|
|
374
|
+
import vCard2 from "vcf";
|
|
375
|
+
import z from "zod";
|
|
376
|
+
var userRefSchema = z.object({
|
|
377
|
+
__platform: z.string(),
|
|
378
|
+
id: z.string()
|
|
379
|
+
});
|
|
380
|
+
var nameSchema = z.object({
|
|
381
|
+
formatted: z.string().optional(),
|
|
382
|
+
first: z.string().optional(),
|
|
383
|
+
last: z.string().optional(),
|
|
384
|
+
middle: z.string().optional(),
|
|
385
|
+
prefix: z.string().optional(),
|
|
386
|
+
suffix: z.string().optional()
|
|
387
|
+
});
|
|
388
|
+
var phoneTypeSchema = z.enum(["mobile", "home", "work", "other"]);
|
|
389
|
+
var emailTypeSchema = z.enum(["home", "work", "other"]);
|
|
390
|
+
var addressTypeSchema = z.enum(["home", "work", "other"]);
|
|
391
|
+
var phoneSchema = z.object({
|
|
392
|
+
value: z.string(),
|
|
393
|
+
type: phoneTypeSchema.optional()
|
|
394
|
+
});
|
|
395
|
+
var emailSchema = z.object({
|
|
396
|
+
value: z.string(),
|
|
397
|
+
type: emailTypeSchema.optional()
|
|
398
|
+
});
|
|
399
|
+
var addressSchema = z.object({
|
|
400
|
+
street: z.string().optional(),
|
|
401
|
+
city: z.string().optional(),
|
|
402
|
+
region: z.string().optional(),
|
|
403
|
+
postalCode: z.string().optional(),
|
|
404
|
+
country: z.string().optional(),
|
|
405
|
+
type: addressTypeSchema.optional()
|
|
406
|
+
});
|
|
407
|
+
var orgSchema = z.object({
|
|
408
|
+
name: z.string().optional(),
|
|
409
|
+
title: z.string().optional(),
|
|
410
|
+
department: z.string().optional()
|
|
411
|
+
});
|
|
412
|
+
var photoSchema = z.object({
|
|
413
|
+
mimeType: z.string(),
|
|
414
|
+
read: readSchema
|
|
415
|
+
});
|
|
416
|
+
var contactSchema = z.object({
|
|
417
|
+
type: z.literal("contact"),
|
|
418
|
+
user: userRefSchema.optional(),
|
|
419
|
+
name: nameSchema.optional(),
|
|
420
|
+
phones: z.array(phoneSchema).optional(),
|
|
421
|
+
emails: z.array(emailSchema).optional(),
|
|
422
|
+
addresses: z.array(addressSchema).optional(),
|
|
423
|
+
org: orgSchema.optional(),
|
|
424
|
+
urls: z.array(z.string()).optional(),
|
|
425
|
+
birthday: z.string().optional(),
|
|
426
|
+
note: z.string().optional(),
|
|
427
|
+
photo: photoSchema.optional(),
|
|
428
|
+
raw: z.unknown().optional()
|
|
429
|
+
});
|
|
430
|
+
var asContact = (input) => contactSchema.parse({ type: "contact", ...input });
|
|
431
|
+
var isUser = (value) => typeof value === "object" && value !== null && "__platform" in value && "id" in value && typeof value.__platform === "string" && typeof value.id === "string";
|
|
432
|
+
function contact(input, details) {
|
|
433
|
+
return {
|
|
434
|
+
build: async () => {
|
|
435
|
+
if (typeof input === "string") {
|
|
436
|
+
return asContact(fromVCard(input));
|
|
437
|
+
}
|
|
438
|
+
if (input instanceof vCard2) {
|
|
439
|
+
return asContact(fromVCard(input.toString()));
|
|
440
|
+
}
|
|
441
|
+
if (isUser(input)) {
|
|
442
|
+
return asContact({
|
|
443
|
+
user: { __platform: input.__platform, id: input.id },
|
|
444
|
+
...details
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return asContact(input);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export {
|
|
453
|
+
fromVCard,
|
|
454
|
+
toVCard,
|
|
455
|
+
asContact,
|
|
456
|
+
contact
|
|
457
|
+
};
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
-
asPollOption
|
|
2
|
+
asPollOption
|
|
3
|
+
} from "./chunk-KO67KDBD.js";
|
|
4
|
+
import {
|
|
3
5
|
cloud,
|
|
4
6
|
mergeStreams,
|
|
5
7
|
stream
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YKWKZ2PZ.js";
|
|
9
|
+
import {
|
|
10
|
+
asContact
|
|
11
|
+
} from "./chunk-LCJMR75R.js";
|
|
7
12
|
import {
|
|
8
13
|
UnsupportedError,
|
|
9
14
|
asAttachment,
|
|
10
|
-
asContact,
|
|
11
15
|
asCustom,
|
|
12
16
|
asReaction,
|
|
13
17
|
asText,
|
|
14
18
|
definePlatform
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-3BTOWGQL.js";
|
|
16
20
|
|
|
17
21
|
// src/providers/whatsapp-business/index.ts
|
|
18
22
|
import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
|