spectrum-ts 0.9.1 → 1.1.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.
- package/dist/chunk-7Q7KJKGL.js +117 -0
- package/dist/{chunk-HU2EOF3K.js → chunk-LAGNM6I7.js} +2 -4
- package/dist/chunk-PXX7ISZ6.js +188 -0
- package/dist/{chunk-U6WCQVVX.js → chunk-XMAI2AAN.js} +590 -6
- package/dist/index.d.ts +38 -3
- package/dist/index.js +29 -132
- package/dist/providers/imessage/index.d.ts +1 -1
- package/dist/providers/imessage/index.js +353 -21
- package/dist/providers/terminal/index.d.ts +129 -9
- package/dist/providers/terminal/index.js +813 -32
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +98 -10
- package/dist/{types-D5KhSXLy.d.ts → types-DJQLFwWW.d.ts} +20 -0
- package/package.json +2 -2
- package/dist/chunk-OIXH5S65.js +0 -712
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bufferToStream,
|
|
3
|
+
readSchema,
|
|
4
|
+
streamSchema
|
|
5
|
+
} from "./chunk-XMAI2AAN.js";
|
|
6
|
+
|
|
7
|
+
// src/content/voice.ts
|
|
8
|
+
import { createReadStream } from "fs";
|
|
9
|
+
import { readFile, stat } from "fs/promises";
|
|
10
|
+
import { basename } from "path";
|
|
11
|
+
import { Readable } from "stream";
|
|
12
|
+
import { lookup as lookupMimeType } from "mime-types";
|
|
13
|
+
import z from "zod";
|
|
14
|
+
var AUDIO_MIME_PATTERN = /^audio\//i;
|
|
15
|
+
var audioMimeSchema = z.string().nonempty().regex(AUDIO_MIME_PATTERN, "voice content requires an audio/* MIME type");
|
|
16
|
+
var voiceSchema = z.object({
|
|
17
|
+
type: z.literal("voice"),
|
|
18
|
+
name: z.string().nonempty().optional(),
|
|
19
|
+
mimeType: audioMimeSchema,
|
|
20
|
+
duration: z.number().nonnegative().optional(),
|
|
21
|
+
size: z.number().int().nonnegative().optional(),
|
|
22
|
+
read: readSchema,
|
|
23
|
+
stream: streamSchema
|
|
24
|
+
});
|
|
25
|
+
var resolveVoiceName = (input, name) => {
|
|
26
|
+
if (name) {
|
|
27
|
+
return name;
|
|
28
|
+
}
|
|
29
|
+
if (typeof input === "string") {
|
|
30
|
+
return basename(input);
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
};
|
|
34
|
+
var resolveVoiceMimeType = (name, mimeType) => {
|
|
35
|
+
if (mimeType) {
|
|
36
|
+
if (!AUDIO_MIME_PATTERN.test(mimeType)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`voice content requires an audio/* MIME type, got "${mimeType}".`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return mimeType;
|
|
42
|
+
}
|
|
43
|
+
if (name) {
|
|
44
|
+
const resolved = lookupMimeType(name);
|
|
45
|
+
if (resolved && AUDIO_MIME_PATTERN.test(resolved)) {
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
|
48
|
+
if (resolved) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Resolved non-audio MIME type "${resolved}" from name "${name}". Pass options.mimeType explicitly with an audio/* type.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Unable to resolve MIME type for voice content. Pass options.mimeType explicitly."
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
var asVoice = (input) => {
|
|
59
|
+
let cached;
|
|
60
|
+
const read = () => {
|
|
61
|
+
cached ??= input.read().catch((err) => {
|
|
62
|
+
cached = void 0;
|
|
63
|
+
throw err;
|
|
64
|
+
});
|
|
65
|
+
return cached;
|
|
66
|
+
};
|
|
67
|
+
const stream = input.stream ?? (async () => bufferToStream(await read()));
|
|
68
|
+
return voiceSchema.parse({
|
|
69
|
+
type: "voice",
|
|
70
|
+
name: input.name,
|
|
71
|
+
mimeType: input.mimeType,
|
|
72
|
+
duration: input.duration,
|
|
73
|
+
size: input.size,
|
|
74
|
+
read,
|
|
75
|
+
stream
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
function voice(input, options) {
|
|
79
|
+
return {
|
|
80
|
+
build: async () => {
|
|
81
|
+
const name = resolveVoiceName(input, options?.name);
|
|
82
|
+
const mimeHint = typeof input === "string" ? basename(input) : name;
|
|
83
|
+
const mimeType = resolveVoiceMimeType(mimeHint, options?.mimeType);
|
|
84
|
+
if (typeof input === "string") {
|
|
85
|
+
const stats = await stat(input);
|
|
86
|
+
if (!stats.isFile()) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`voice content path "${input}" is not a regular file.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return asVoice({
|
|
92
|
+
name,
|
|
93
|
+
mimeType,
|
|
94
|
+
duration: options?.duration,
|
|
95
|
+
size: stats.size,
|
|
96
|
+
read: () => readFile(input),
|
|
97
|
+
stream: async () => Readable.toWeb(
|
|
98
|
+
createReadStream(input)
|
|
99
|
+
)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return asVoice({
|
|
103
|
+
name,
|
|
104
|
+
mimeType,
|
|
105
|
+
duration: options?.duration,
|
|
106
|
+
size: input.byteLength,
|
|
107
|
+
read: async () => input,
|
|
108
|
+
stream: async () => bufferToStream(input)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
asVoice,
|
|
116
|
+
voice
|
|
117
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// src/content/poll.ts
|
|
2
|
+
import z from "zod";
|
|
3
|
+
var pollChoiceSchema = z.object({
|
|
4
|
+
title: z.string().nonempty()
|
|
5
|
+
});
|
|
6
|
+
var pollSchema = z.object({
|
|
7
|
+
type: z.literal("poll"),
|
|
8
|
+
title: z.string().nonempty().max(300),
|
|
9
|
+
options: z.array(pollChoiceSchema).min(2).max(10)
|
|
10
|
+
});
|
|
11
|
+
var pollOptionSchema = z.object({
|
|
12
|
+
type: z.literal("poll_option"),
|
|
13
|
+
option: pollChoiceSchema,
|
|
14
|
+
poll: pollSchema,
|
|
15
|
+
selected: z.boolean(),
|
|
16
|
+
title: z.string().nonempty()
|
|
17
|
+
}).superRefine((value, ctx) => {
|
|
18
|
+
if (value.title !== value.option.title) {
|
|
19
|
+
ctx.addIssue({
|
|
20
|
+
code: "custom",
|
|
21
|
+
message: "poll_option title must match option.title",
|
|
22
|
+
path: ["title"]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (!value.poll.options.some(
|
|
26
|
+
(pollOption) => pollOption.title === value.option.title
|
|
27
|
+
)) {
|
|
28
|
+
ctx.addIssue({
|
|
29
|
+
code: "custom",
|
|
30
|
+
message: "poll_option option must exist in poll.options",
|
|
31
|
+
path: ["option"]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
var asPoll = (input) => pollSchema.parse({ type: "poll", ...input });
|
|
36
|
+
var asPollOption = (input) => pollOptionSchema.parse({
|
|
37
|
+
type: "poll_option",
|
|
38
|
+
...input,
|
|
39
|
+
title: input.option.title
|
|
40
|
+
});
|
|
41
|
+
var option = (title) => ({ title });
|
|
42
|
+
var normalize = (raw) => typeof raw === "string" ? { title: raw } : { title: raw.title };
|
|
43
|
+
var collectOptions = (args) => {
|
|
44
|
+
const [first] = args;
|
|
45
|
+
if (args.length === 1 && Array.isArray(first)) {
|
|
46
|
+
return first;
|
|
47
|
+
}
|
|
48
|
+
return args;
|
|
49
|
+
};
|
|
50
|
+
function poll(title, ...rest) {
|
|
51
|
+
return {
|
|
52
|
+
build: async () => asPoll({ title, options: collectOptions(rest).map(normalize) })
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/utils/stream.ts
|
|
57
|
+
import { Repeater } from "@repeaterjs/repeater";
|
|
58
|
+
function stream(setup) {
|
|
59
|
+
const repeater = new Repeater(async (push, stop) => {
|
|
60
|
+
const emit = async (value) => {
|
|
61
|
+
try {
|
|
62
|
+
await push(value);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
stop(error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const end = (error) => {
|
|
69
|
+
stop(error);
|
|
70
|
+
};
|
|
71
|
+
const cleanup = await setup(emit, end);
|
|
72
|
+
try {
|
|
73
|
+
await stop;
|
|
74
|
+
} finally {
|
|
75
|
+
await cleanup?.();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return Object.assign(repeater, {
|
|
79
|
+
close: async () => {
|
|
80
|
+
await repeater.return(void 0);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function mergeStreams(streams) {
|
|
85
|
+
return stream((emit, end) => {
|
|
86
|
+
if (streams.length === 0) {
|
|
87
|
+
end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let openStreams = streams.length;
|
|
91
|
+
const workers = streams.map(async (source) => {
|
|
92
|
+
try {
|
|
93
|
+
for await (const value of source) {
|
|
94
|
+
await emit(value);
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
end(error);
|
|
98
|
+
} finally {
|
|
99
|
+
openStreams -= 1;
|
|
100
|
+
if (openStreams === 0) {
|
|
101
|
+
end();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return async () => {
|
|
106
|
+
await Promise.allSettled(streams.map((source) => source.close()));
|
|
107
|
+
await Promise.allSettled(workers);
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/utils/cloud.ts
|
|
113
|
+
var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum.photon.codes"}`;
|
|
114
|
+
var SpectrumCloudError = class extends Error {
|
|
115
|
+
status;
|
|
116
|
+
code;
|
|
117
|
+
constructor(status, code, message) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = "SpectrumCloudError";
|
|
120
|
+
this.status = status;
|
|
121
|
+
this.code = code;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var request = async (path, init) => {
|
|
125
|
+
const response = await fetch(`${SPECTRUM_CLOUD_URL}${path}`, init);
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const body = await response.text().catch(() => "");
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(body);
|
|
130
|
+
throw new SpectrumCloudError(
|
|
131
|
+
response.status,
|
|
132
|
+
parsed.code,
|
|
133
|
+
parsed.message
|
|
134
|
+
);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof SpectrumCloudError) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
throw new SpectrumCloudError(
|
|
140
|
+
response.status,
|
|
141
|
+
"UNKNOWN",
|
|
142
|
+
body || response.statusText
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const json = await response.json();
|
|
147
|
+
if (!json.succeed) {
|
|
148
|
+
throw new SpectrumCloudError(
|
|
149
|
+
response.status,
|
|
150
|
+
"UNKNOWN",
|
|
151
|
+
"Server returned succeed=false"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return json.data;
|
|
155
|
+
};
|
|
156
|
+
var basicAuth = (projectId, projectSecret) => `Basic ${btoa(`${projectId}:${projectSecret}`)}`;
|
|
157
|
+
var cloud = {
|
|
158
|
+
getSubscription: (projectId) => request(`/projects/${projectId}/billing/subscription`),
|
|
159
|
+
issueImessageTokens: (projectId, projectSecret) => request(`/projects/${projectId}/imessage/tokens`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: { Authorization: basicAuth(projectId, projectSecret) }
|
|
162
|
+
}),
|
|
163
|
+
getImessageInfo: (projectId) => request(`/projects/${projectId}/imessage/`),
|
|
164
|
+
issueWhatsappBusinessTokens: (projectId, projectSecret) => request(`/projects/${projectId}/whatsapp-business/tokens`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { Authorization: basicAuth(projectId, projectSecret) }
|
|
167
|
+
}),
|
|
168
|
+
getPlatforms: (projectId) => request(`/projects/${projectId}/platforms/`),
|
|
169
|
+
togglePlatform: (projectId, projectSecret, platform, enabled) => request(`/projects/${projectId}/platforms/`, {
|
|
170
|
+
method: "PATCH",
|
|
171
|
+
headers: {
|
|
172
|
+
Authorization: basicAuth(projectId, projectSecret),
|
|
173
|
+
"Content-Type": "application/json"
|
|
174
|
+
},
|
|
175
|
+
body: JSON.stringify({ platform, enabled })
|
|
176
|
+
})
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
asPoll,
|
|
181
|
+
asPollOption,
|
|
182
|
+
option,
|
|
183
|
+
poll,
|
|
184
|
+
stream,
|
|
185
|
+
mergeStreams,
|
|
186
|
+
SpectrumCloudError,
|
|
187
|
+
cloud
|
|
188
|
+
};
|