spectrum-ts 0.1.1 → 0.2.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-UZ2CXPOD.js → chunk-LIRM7SBA.js} +3 -2
- package/dist/chunk-XOBTWTFC.js +67 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.js +24 -14
- package/dist/providers/imessage/index.d.ts +1 -1
- package/dist/providers/imessage/index.js +12 -32
- package/dist/providers/terminal/index.d.ts +1 -1
- package/dist/providers/terminal/index.js +1 -1
- package/dist/providers/whatsapp-business/index.d.ts +41 -0
- package/dist/providers/whatsapp-business/index.js +280 -0
- package/dist/{types-DiKuSemh.d.ts → types-DQE0dQT4.d.ts} +3 -3
- package/package.json +7 -1
- package/src/index.ts +12 -0
- package/src/platform/define.ts +3 -4
- package/src/platform/types.ts +7 -3
- package/src/providers/imessage/auth.ts +8 -54
- package/src/providers/imessage/index.ts +8 -0
- package/src/providers/whatsapp-business/index.ts +77 -0
- package/src/providers/whatsapp-business/messages.ts +240 -0
- package/src/providers/whatsapp-business/types.ts +19 -0
- package/src/spectrum.ts +28 -15
- package/src/utils/cloud.ts +146 -0
|
@@ -155,12 +155,13 @@ function definePlatform(name, def) {
|
|
|
155
155
|
}
|
|
156
156
|
throw new Error("Invalid input to platform narrowing function");
|
|
157
157
|
});
|
|
158
|
-
narrower.config = (config
|
|
158
|
+
narrower.config = (config) => {
|
|
159
|
+
const resolvedConfig = config ?? {};
|
|
159
160
|
return {
|
|
160
161
|
__tag: "PlatformProviderConfig",
|
|
161
162
|
__def: void 0,
|
|
162
163
|
__name: name,
|
|
163
|
-
config,
|
|
164
|
+
config: resolvedConfig,
|
|
164
165
|
__definition: fullDef
|
|
165
166
|
};
|
|
166
167
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/utils/cloud.ts
|
|
2
|
+
var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum-cloud.photon.codes"}`;
|
|
3
|
+
var SpectrumCloudError = class extends Error {
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
constructor(status, code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SpectrumCloudError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var request = async (path, init) => {
|
|
14
|
+
const response = await fetch(`${SPECTRUM_CLOUD_URL}${path}`, init);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const body = await response.text().catch(() => "");
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(body);
|
|
19
|
+
throw new SpectrumCloudError(
|
|
20
|
+
response.status,
|
|
21
|
+
parsed.code,
|
|
22
|
+
parsed.message
|
|
23
|
+
);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof SpectrumCloudError) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
throw new SpectrumCloudError(
|
|
29
|
+
response.status,
|
|
30
|
+
"UNKNOWN",
|
|
31
|
+
body || response.statusText
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const json = await response.json();
|
|
36
|
+
if (!json.succeed) {
|
|
37
|
+
throw new SpectrumCloudError(
|
|
38
|
+
response.status,
|
|
39
|
+
"UNKNOWN",
|
|
40
|
+
"Server returned succeed=false"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return json.data;
|
|
44
|
+
};
|
|
45
|
+
var basicAuth = (projectId, projectSecret) => `Basic ${btoa(`${projectId}:${projectSecret}`)}`;
|
|
46
|
+
var cloud = {
|
|
47
|
+
getSubscription: (projectId) => request(`/projects/${projectId}/billing/subscription`),
|
|
48
|
+
issueImessageTokens: (projectId, projectSecret) => request(`/projects/${projectId}/imessage/tokens`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { Authorization: basicAuth(projectId, projectSecret) }
|
|
51
|
+
}),
|
|
52
|
+
getImessageInfo: (projectId) => request(`/projects/${projectId}/imessage/`),
|
|
53
|
+
getPlatforms: (projectId) => request(`/projects/${projectId}/platforms/`),
|
|
54
|
+
togglePlatform: (projectId, projectSecret, platform, enabled) => request(`/projects/${projectId}/platforms/`, {
|
|
55
|
+
method: "PATCH",
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: basicAuth(projectId, projectSecret),
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({ platform, enabled })
|
|
61
|
+
})
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
SpectrumCloudError,
|
|
66
|
+
cloud
|
|
67
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z__default from 'zod';
|
|
2
|
-
import { P as ProviderMessage, a as PlatformDef, b as Platform, c as PlatformProviderConfig, S as SpectrumLike, C as CustomEventStreams, d as Space, M as Message, e as ContentBuilder } from './types-
|
|
3
|
-
export { A as AnyPlatformDef, f as Content, E as EventProducer, g as PlatformInstance, h as PlatformMessage, i as PlatformSpace, j as PlatformUser, k as SchemaMessage, U as User, l as attachment, m as custom, t as text } from './types-
|
|
2
|
+
import { P as ProviderMessage, a as PlatformDef, b as Platform, c as PlatformProviderConfig, S as SpectrumLike, C as CustomEventStreams, d as Space, M as Message, e as ContentBuilder } from './types-DQE0dQT4.js';
|
|
3
|
+
export { A as AnyPlatformDef, f as Content, E as EventProducer, g as PlatformInstance, h as PlatformMessage, i as PlatformSpace, j as PlatformUser, k as SchemaMessage, U as User, l as attachment, m as custom, t as text } from './types-DQE0dQT4.js';
|
|
4
4
|
export { M as ManagedStream, m as mergeStreams, s as stream } from './stream-DGy4geUK.js';
|
|
5
5
|
import 'hotscript';
|
|
6
6
|
import 'type-fest';
|
|
@@ -29,8 +29,51 @@ type SpectrumInstance<Providers extends PlatformProviderConfig[] = PlatformProvi
|
|
|
29
29
|
send(space: Space, ...content: [ContentBuilder, ...ContentBuilder[]]): Promise<void>;
|
|
30
30
|
responding<T>(space: Space, fn: () => T | Promise<T>): Promise<T>;
|
|
31
31
|
};
|
|
32
|
-
declare function Spectrum<const Providers extends PlatformProviderConfig[]>(
|
|
32
|
+
declare function Spectrum<const Providers extends PlatformProviderConfig[]>(options: {
|
|
33
|
+
projectId: string;
|
|
34
|
+
projectSecret: string;
|
|
35
|
+
providers: [...Providers];
|
|
36
|
+
} | {
|
|
37
|
+
projectId?: never;
|
|
38
|
+
projectSecret?: never;
|
|
33
39
|
providers: [...Providers];
|
|
34
40
|
}): Promise<SpectrumInstance<Providers>>;
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
type SubscriptionStatus = "active" | "canceled" | "past_due";
|
|
43
|
+
interface SubscriptionData {
|
|
44
|
+
status: SubscriptionStatus | null;
|
|
45
|
+
tier: string;
|
|
46
|
+
}
|
|
47
|
+
interface SharedTokenData {
|
|
48
|
+
expiresIn: number;
|
|
49
|
+
token: string;
|
|
50
|
+
type: "shared";
|
|
51
|
+
}
|
|
52
|
+
interface DedicatedTokenData {
|
|
53
|
+
auth: Record<string, string>;
|
|
54
|
+
expiresIn: number;
|
|
55
|
+
type: "dedicated";
|
|
56
|
+
}
|
|
57
|
+
type TokenData = SharedTokenData | DedicatedTokenData;
|
|
58
|
+
type CloudPlatform = "imessage" | "whatsapp_business";
|
|
59
|
+
interface PlatformStatus {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
}
|
|
62
|
+
type PlatformsData = Record<CloudPlatform, PlatformStatus>;
|
|
63
|
+
interface ImessageInfoData {
|
|
64
|
+
type: "shared" | "dedicated";
|
|
65
|
+
}
|
|
66
|
+
declare class SpectrumCloudError extends Error {
|
|
67
|
+
readonly status: number;
|
|
68
|
+
readonly code: string;
|
|
69
|
+
constructor(status: number, code: string, message: string);
|
|
70
|
+
}
|
|
71
|
+
declare const cloud: {
|
|
72
|
+
getSubscription: (projectId: string) => Promise<SubscriptionData>;
|
|
73
|
+
issueImessageTokens: (projectId: string, projectSecret: string) => Promise<TokenData>;
|
|
74
|
+
getImessageInfo: (projectId: string) => Promise<ImessageInfoData>;
|
|
75
|
+
getPlatforms: (projectId: string) => Promise<PlatformsData>;
|
|
76
|
+
togglePlatform: (projectId: string, projectSecret: string, platform: CloudPlatform, enabled: boolean) => Promise<PlatformsData>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { type CloudPlatform, ContentBuilder, type DedicatedTokenData, type ImessageInfoData, Message, Platform, PlatformDef, PlatformProviderConfig, type PlatformStatus, type PlatformsData, type SharedTokenData, Space, Spectrum, SpectrumCloudError, type SpectrumInstance, type SubscriptionData, type SubscriptionStatus, type TokenData, cloud, definePlatform };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SpectrumCloudError,
|
|
3
|
+
cloud
|
|
4
|
+
} from "./chunk-XOBTWTFC.js";
|
|
1
5
|
import {
|
|
2
6
|
mergeStreams,
|
|
3
7
|
stream
|
|
4
8
|
} from "./chunk-3TBRO2J7.js";
|
|
5
9
|
import {
|
|
6
10
|
definePlatform
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LIRM7SBA.js";
|
|
8
12
|
|
|
9
13
|
// src/spectrum.ts
|
|
10
14
|
import z from "zod";
|
|
@@ -15,21 +19,25 @@ var providerMessageCoreKeys = /* @__PURE__ */ new Set([
|
|
|
15
19
|
"space",
|
|
16
20
|
"timestamp"
|
|
17
21
|
]);
|
|
18
|
-
var spectrumConfigSchema = z.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
projectId,
|
|
26
|
-
projectSecret,
|
|
27
|
-
providers:
|
|
28
|
-
})
|
|
22
|
+
var spectrumConfigSchema = z.union([
|
|
23
|
+
z.object({
|
|
24
|
+
projectId: z.string().min(1),
|
|
25
|
+
projectSecret: z.string().min(1),
|
|
26
|
+
providers: z.array(z.custom())
|
|
27
|
+
}),
|
|
28
|
+
z.object({
|
|
29
|
+
projectId: z.undefined().optional(),
|
|
30
|
+
projectSecret: z.undefined().optional(),
|
|
31
|
+
providers: z.array(z.custom())
|
|
32
|
+
})
|
|
33
|
+
]);
|
|
34
|
+
async function Spectrum(options) {
|
|
35
|
+
spectrumConfigSchema.parse(options);
|
|
36
|
+
const { projectId, projectSecret, providers } = options;
|
|
29
37
|
const platformStates = /* @__PURE__ */ new Map();
|
|
30
38
|
const customEventStreams = /* @__PURE__ */ new Map();
|
|
31
39
|
let stopped = false;
|
|
32
|
-
for (const provider of
|
|
40
|
+
for (const provider of providers) {
|
|
33
41
|
const providerConfig = provider;
|
|
34
42
|
const def = providerConfig.__definition;
|
|
35
43
|
const userConfig = def.config.parse(providerConfig.config);
|
|
@@ -250,7 +258,7 @@ async function Spectrum(projectId, projectSecret, options) {
|
|
|
250
258
|
}
|
|
251
259
|
);
|
|
252
260
|
const base = {
|
|
253
|
-
__providers:
|
|
261
|
+
__providers: providers,
|
|
254
262
|
__internal: { platforms: platformStates },
|
|
255
263
|
messages,
|
|
256
264
|
stop: stopOnce,
|
|
@@ -335,7 +343,9 @@ function attachment(input, options) {
|
|
|
335
343
|
}
|
|
336
344
|
export {
|
|
337
345
|
Spectrum,
|
|
346
|
+
SpectrumCloudError,
|
|
338
347
|
attachment,
|
|
348
|
+
cloud,
|
|
339
349
|
custom,
|
|
340
350
|
definePlatform,
|
|
341
351
|
mergeStreams,
|
|
@@ -3,7 +3,7 @@ import { AdvancedIMessage } from '@photon-ai/advanced-imessage';
|
|
|
3
3
|
import { IMessageSDK } from '@photon-ai/imessage-kit';
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import z__default from 'zod';
|
|
6
|
-
import { k as SchemaMessage, b as Platform, a as PlatformDef, P as ProviderMessage } from '../../types-
|
|
6
|
+
import { k as SchemaMessage, b as Platform, a as PlatformDef, P as ProviderMessage } from '../../types-DQE0dQT4.js';
|
|
7
7
|
import * as zod_v4_core from 'zod/v4/core';
|
|
8
8
|
import 'hotscript';
|
|
9
9
|
import 'type-fest';
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cloud
|
|
3
|
+
} from "../../chunk-XOBTWTFC.js";
|
|
1
4
|
import {
|
|
2
5
|
mergeStreams,
|
|
3
6
|
stream
|
|
4
7
|
} from "../../chunk-3TBRO2J7.js";
|
|
5
8
|
import {
|
|
6
9
|
definePlatform
|
|
7
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-LIRM7SBA.js";
|
|
8
11
|
|
|
9
12
|
// src/providers/imessage/index.ts
|
|
10
13
|
import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
|
|
@@ -14,40 +17,12 @@ import { IMessageSDK as IMessageSDK2 } from "@photon-ai/imessage-kit";
|
|
|
14
17
|
import {
|
|
15
18
|
createClient
|
|
16
19
|
} from "@photon-ai/advanced-imessage";
|
|
17
|
-
|
|
18
|
-
// src/utils/cloud.ts
|
|
19
|
-
var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum-cloud.photon.codes"}`;
|
|
20
|
-
|
|
21
|
-
// src/providers/imessage/auth.ts
|
|
22
20
|
var RENEWAL_RATIO = 0.8;
|
|
23
21
|
var EXPIRY_BUFFER_MS = 3e4;
|
|
24
22
|
var RETRY_DELAY_MS = 3e4;
|
|
25
23
|
var cloudAuthState = /* @__PURE__ */ new WeakMap();
|
|
26
|
-
async function fetchTokens(projectId, projectSecret) {
|
|
27
|
-
const url = `${SPECTRUM_CLOUD_URL}/${projectId}/imessage/tokens`;
|
|
28
|
-
const credentials = btoa(`${projectId}:${projectSecret}`);
|
|
29
|
-
const response = await fetch(url, {
|
|
30
|
-
method: "POST",
|
|
31
|
-
headers: {
|
|
32
|
-
Authorization: `Basic ${credentials}`
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
if (!response.ok) {
|
|
36
|
-
const body = await response.text().catch(() => "");
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Spectrum Cloud authentication failed (${response.status}): ${body || response.statusText}`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
const json = await response.json();
|
|
42
|
-
if (!json.succeed) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
"Spectrum Cloud authentication failed: server returned succeed=false"
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
return json.data;
|
|
48
|
-
}
|
|
49
24
|
async function createCloudClients(projectId, projectSecret) {
|
|
50
|
-
let tokenData = await
|
|
25
|
+
let tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
51
26
|
let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
52
27
|
let disposed = false;
|
|
53
28
|
let renewalTimer;
|
|
@@ -59,7 +34,7 @@ async function createCloudClients(projectId, projectSecret) {
|
|
|
59
34
|
const renewInMs = Math.max(ttlMs * RENEWAL_RATIO, 5e3);
|
|
60
35
|
renewalTimer = setTimeout(async () => {
|
|
61
36
|
try {
|
|
62
|
-
tokenData = await
|
|
37
|
+
tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
63
38
|
tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
64
39
|
scheduleRenewal();
|
|
65
40
|
} catch {
|
|
@@ -74,7 +49,7 @@ async function createCloudClients(projectId, projectSecret) {
|
|
|
74
49
|
if (Date.now() < tokenExpiresAt - EXPIRY_BUFFER_MS) {
|
|
75
50
|
return;
|
|
76
51
|
}
|
|
77
|
-
tokenData = await
|
|
52
|
+
tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
78
53
|
tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
79
54
|
scheduleRenewal();
|
|
80
55
|
};
|
|
@@ -356,6 +331,11 @@ var imessage = definePlatform("iMessage", {
|
|
|
356
331
|
(e) => createClient2({ address: e.address, tls: true, token: e.token })
|
|
357
332
|
);
|
|
358
333
|
}
|
|
334
|
+
if (!(projectId && projectSecret)) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"iMessage requires projectId and projectSecret. Either pass credentials to Spectrum(), use local mode: imessage.config({ local: true }), or provide explicit client config: imessage.config({ clients: [...] })"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
359
339
|
return await createCloudClients(projectId, projectSecret);
|
|
360
340
|
},
|
|
361
341
|
destroyClient: async ({ client }) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as Platform, a as PlatformDef, P as ProviderMessage } from '../../types-
|
|
1
|
+
import { b as Platform, a as PlatformDef, P as ProviderMessage } from '../../types-DQE0dQT4.js';
|
|
2
2
|
import * as node_readline from 'node:readline';
|
|
3
3
|
import z__default from 'zod';
|
|
4
4
|
import 'hotscript';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { M as ManagedStream } from '../../stream-DGy4geUK.js';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import z__default from 'zod';
|
|
4
|
+
import { k as SchemaMessage, b as Platform, a as PlatformDef, P as ProviderMessage } from '../../types-DQE0dQT4.js';
|
|
5
|
+
import * as zod_v4_core from 'zod/v4/core';
|
|
6
|
+
import { WhatsAppClient } from '@photon-ai/whatsapp-business';
|
|
7
|
+
import 'hotscript';
|
|
8
|
+
import 'type-fest';
|
|
9
|
+
|
|
10
|
+
declare const userSchema: z__default.ZodObject<{}, z__default.core.$strip>;
|
|
11
|
+
declare const spaceSchema: z__default.ZodObject<{
|
|
12
|
+
id: z__default.ZodString;
|
|
13
|
+
}, z__default.core.$strip>;
|
|
14
|
+
type WhatsAppMessage = SchemaMessage<typeof userSchema, typeof spaceSchema>;
|
|
15
|
+
|
|
16
|
+
declare const whatsappBusiness: Platform<PlatformDef<"WhatsApp Business", z.ZodObject<{
|
|
17
|
+
accessToken: z.ZodString;
|
|
18
|
+
phoneNumberId: z.ZodString;
|
|
19
|
+
appSecret: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, zod_v4_core.$strip>, z.ZodType<object, unknown, zod_v4_core.$ZodTypeInternals<object, unknown>> | undefined, z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
}, zod_v4_core.$strip>, z.ZodType<object, unknown, zod_v4_core.$ZodTypeInternals<object, unknown>> | undefined, WhatsAppClient, {
|
|
23
|
+
id: string;
|
|
24
|
+
}, {
|
|
25
|
+
id: string;
|
|
26
|
+
}, undefined, ProviderMessage<{
|
|
27
|
+
id: string;
|
|
28
|
+
}, {
|
|
29
|
+
id: string;
|
|
30
|
+
}, Record<never, never>>, {
|
|
31
|
+
messages: ({ client }: {
|
|
32
|
+
client: WhatsAppClient;
|
|
33
|
+
config: {
|
|
34
|
+
accessToken: string;
|
|
35
|
+
phoneNumberId: string;
|
|
36
|
+
appSecret?: string | undefined;
|
|
37
|
+
};
|
|
38
|
+
}) => ManagedStream<WhatsAppMessage>;
|
|
39
|
+
}>> & Readonly<Record<never, never>>;
|
|
40
|
+
|
|
41
|
+
export { whatsappBusiness };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stream
|
|
3
|
+
} from "../../chunk-3TBRO2J7.js";
|
|
4
|
+
import {
|
|
5
|
+
definePlatform
|
|
6
|
+
} from "../../chunk-LIRM7SBA.js";
|
|
7
|
+
|
|
8
|
+
// src/providers/whatsapp-business/index.ts
|
|
9
|
+
import {
|
|
10
|
+
createClient
|
|
11
|
+
} from "@photon-ai/whatsapp-business";
|
|
12
|
+
|
|
13
|
+
// src/providers/whatsapp-business/messages.ts
|
|
14
|
+
var toMessage = async (client, msg) => {
|
|
15
|
+
const content = await mapContent(client, msg.content);
|
|
16
|
+
return {
|
|
17
|
+
id: msg.id,
|
|
18
|
+
content,
|
|
19
|
+
sender: { id: msg.from },
|
|
20
|
+
space: { id: msg.from },
|
|
21
|
+
timestamp: msg.timestamp
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
var mapContent = async (client, content) => {
|
|
25
|
+
switch (content.type) {
|
|
26
|
+
case "text":
|
|
27
|
+
return [{ type: "plain_text", text: content.body }];
|
|
28
|
+
case "image":
|
|
29
|
+
case "video":
|
|
30
|
+
case "audio":
|
|
31
|
+
case "document":
|
|
32
|
+
return [await downloadMedia(client, content.media)];
|
|
33
|
+
case "sticker":
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
type: "custom",
|
|
37
|
+
raw: { whatsapp_type: "sticker", ...content.sticker }
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
case "location":
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
type: "custom",
|
|
44
|
+
raw: { whatsapp_type: "location", ...content.location }
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
case "contacts":
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
type: "custom",
|
|
51
|
+
raw: { whatsapp_type: "contacts", contacts: content.contacts }
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
case "reaction":
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
type: "custom",
|
|
58
|
+
raw: { whatsapp_type: "reaction", ...content.reaction }
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
case "interactive":
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
type: "custom",
|
|
65
|
+
raw: { whatsapp_type: "interactive", ...content.interactive }
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
case "button":
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
type: "custom",
|
|
72
|
+
raw: { whatsapp_type: "button", ...content.button }
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
case "order":
|
|
76
|
+
return [
|
|
77
|
+
{ type: "custom", raw: { whatsapp_type: "order", ...content.order } }
|
|
78
|
+
];
|
|
79
|
+
case "system":
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
type: "custom",
|
|
83
|
+
raw: { whatsapp_type: "system", ...content.system }
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
default:
|
|
87
|
+
return [{ type: "custom", raw: { whatsapp_type: "unknown" } }];
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var downloadMedia = async (client, media) => {
|
|
91
|
+
try {
|
|
92
|
+
const { url } = await client.media.getUrl(media.id);
|
|
93
|
+
const response = await fetch(url);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Media download failed: ${response.status}`);
|
|
96
|
+
}
|
|
97
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
98
|
+
return {
|
|
99
|
+
type: "attachment",
|
|
100
|
+
data,
|
|
101
|
+
mimeType: media.mimeType,
|
|
102
|
+
name: media.filename ?? `media-${media.id}`
|
|
103
|
+
};
|
|
104
|
+
} catch {
|
|
105
|
+
return {
|
|
106
|
+
type: "custom",
|
|
107
|
+
raw: {
|
|
108
|
+
whatsapp_type: "media_error",
|
|
109
|
+
mediaId: media.id,
|
|
110
|
+
mimeType: media.mimeType
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var mimeToMediaType = (mimeType) => {
|
|
116
|
+
if (mimeType.startsWith("image/")) {
|
|
117
|
+
return "image";
|
|
118
|
+
}
|
|
119
|
+
if (mimeType.startsWith("video/")) {
|
|
120
|
+
return "video";
|
|
121
|
+
}
|
|
122
|
+
if (mimeType.startsWith("audio/")) {
|
|
123
|
+
return "audio";
|
|
124
|
+
}
|
|
125
|
+
return "document";
|
|
126
|
+
};
|
|
127
|
+
var messages = (client) => {
|
|
128
|
+
const eventStream = client.events.subscribe().filter(
|
|
129
|
+
(e) => e.type === "message"
|
|
130
|
+
);
|
|
131
|
+
return stream((emit, end) => {
|
|
132
|
+
(async () => {
|
|
133
|
+
try {
|
|
134
|
+
for await (const event of eventStream) {
|
|
135
|
+
const msg = await toMessage(client, event.message);
|
|
136
|
+
emit(msg);
|
|
137
|
+
}
|
|
138
|
+
end();
|
|
139
|
+
} catch (e) {
|
|
140
|
+
end(e);
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
return () => eventStream.close();
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
var send = async (client, spaceId, content) => {
|
|
147
|
+
switch (content.type) {
|
|
148
|
+
case "plain_text":
|
|
149
|
+
await client.messages.send({ to: spaceId, text: content.text });
|
|
150
|
+
break;
|
|
151
|
+
case "attachment": {
|
|
152
|
+
const { mediaId } = await client.media.upload({
|
|
153
|
+
file: content.data,
|
|
154
|
+
mimeType: content.mimeType,
|
|
155
|
+
filename: content.name
|
|
156
|
+
});
|
|
157
|
+
const mediaType = mimeToMediaType(content.mimeType);
|
|
158
|
+
const mediaPayload = mediaType === "document" ? { id: mediaId, filename: content.name } : { id: mediaId };
|
|
159
|
+
await client.messages.send({
|
|
160
|
+
to: spaceId,
|
|
161
|
+
[mediaType]: mediaPayload
|
|
162
|
+
});
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var reactToMessage = async (client, spaceId, messageId, reaction) => {
|
|
170
|
+
await client.messages.send({
|
|
171
|
+
to: spaceId,
|
|
172
|
+
reaction: { messageId, emoji: reaction }
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
var replyToMessage = async (client, spaceId, messageId, content) => {
|
|
176
|
+
switch (content.type) {
|
|
177
|
+
case "plain_text":
|
|
178
|
+
await client.messages.send({
|
|
179
|
+
to: spaceId,
|
|
180
|
+
replyTo: messageId,
|
|
181
|
+
text: content.text
|
|
182
|
+
});
|
|
183
|
+
break;
|
|
184
|
+
case "attachment": {
|
|
185
|
+
const { mediaId } = await client.media.upload({
|
|
186
|
+
file: content.data,
|
|
187
|
+
mimeType: content.mimeType,
|
|
188
|
+
filename: content.name
|
|
189
|
+
});
|
|
190
|
+
const mediaType = mimeToMediaType(content.mimeType);
|
|
191
|
+
const mediaPayload = mediaType === "document" ? { id: mediaId, filename: content.name } : { id: mediaId };
|
|
192
|
+
await client.messages.send({
|
|
193
|
+
to: spaceId,
|
|
194
|
+
replyTo: messageId,
|
|
195
|
+
[mediaType]: mediaPayload
|
|
196
|
+
});
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/providers/whatsapp-business/types.ts
|
|
205
|
+
import z from "zod";
|
|
206
|
+
var configSchema = z.object({
|
|
207
|
+
accessToken: z.string().min(1),
|
|
208
|
+
phoneNumberId: z.string().min(1),
|
|
209
|
+
appSecret: z.string().optional()
|
|
210
|
+
});
|
|
211
|
+
var userSchema = z.object({});
|
|
212
|
+
var spaceSchema = z.object({
|
|
213
|
+
id: z.string()
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// src/providers/whatsapp-business/index.ts
|
|
217
|
+
var whatsappBusiness = definePlatform("WhatsApp Business", {
|
|
218
|
+
config: configSchema,
|
|
219
|
+
user: {
|
|
220
|
+
resolve: async ({ input }) => ({ id: input.userID })
|
|
221
|
+
},
|
|
222
|
+
space: {
|
|
223
|
+
schema: spaceSchema,
|
|
224
|
+
resolve: async ({ input }) => {
|
|
225
|
+
if (input.users.length === 0) {
|
|
226
|
+
throw new Error("WhatsApp space creation requires at least one user");
|
|
227
|
+
}
|
|
228
|
+
if (input.users.length > 1) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
"WhatsApp Business API only supports 1:1 conversations"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const user = input.users[0];
|
|
234
|
+
if (!user) {
|
|
235
|
+
throw new Error("WhatsApp space creation requires a user");
|
|
236
|
+
}
|
|
237
|
+
return { id: user.id };
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
lifecycle: {
|
|
241
|
+
createClient: async ({ config }) => {
|
|
242
|
+
return createClient({
|
|
243
|
+
accessToken: config.accessToken,
|
|
244
|
+
phoneNumberId: config.phoneNumberId,
|
|
245
|
+
appSecret: config.appSecret ?? ""
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
destroyClient: async ({ client }) => {
|
|
249
|
+
await client.close();
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
events: {
|
|
253
|
+
messages: ({ client }) => messages(client)
|
|
254
|
+
},
|
|
255
|
+
actions: {
|
|
256
|
+
send: async ({ space, content, client }) => {
|
|
257
|
+
const wa = client;
|
|
258
|
+
for (const item of content) {
|
|
259
|
+
await send(wa, space.id, item);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
reactToMessage: async ({ space, messageId, reaction, client }) => {
|
|
263
|
+
await reactToMessage(
|
|
264
|
+
client,
|
|
265
|
+
space.id,
|
|
266
|
+
messageId,
|
|
267
|
+
reaction
|
|
268
|
+
);
|
|
269
|
+
},
|
|
270
|
+
replyToMessage: async ({ space, messageId, content, client }) => {
|
|
271
|
+
const wa = client;
|
|
272
|
+
for (const item of content) {
|
|
273
|
+
await replyToMessage(wa, space.id, messageId, item);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
export {
|
|
279
|
+
whatsappBusiness
|
|
280
|
+
};
|
|
@@ -117,8 +117,8 @@ interface PlatformDef<_Name extends string = string, _ConfigSchema extends z__de
|
|
|
117
117
|
lifecycle: {
|
|
118
118
|
createClient: (ctx: {
|
|
119
119
|
config: z__default.infer<_ConfigSchema>;
|
|
120
|
-
projectId: string;
|
|
121
|
-
projectSecret: string;
|
|
120
|
+
projectId: string | undefined;
|
|
121
|
+
projectSecret: string | undefined;
|
|
122
122
|
}) => Promise<_Client>;
|
|
123
123
|
destroyClient: (ctx: {
|
|
124
124
|
client: _Client;
|
|
@@ -252,7 +252,7 @@ interface SpectrumLike<Providers extends PlatformProviderConfig[] = PlatformProv
|
|
|
252
252
|
readonly __providers: Providers;
|
|
253
253
|
}
|
|
254
254
|
interface Platform<Def extends AnyPlatformDef> {
|
|
255
|
-
config(config?: z__default.input<Def["config"]>): PlatformProviderConfig<Def>;
|
|
255
|
+
config(...args: Record<string, never> extends z__default.input<Def["config"]> ? [config?: z__default.input<Def["config"]>] : [config: z__default.input<Def["config"]>]): PlatformProviderConfig<Def>;
|
|
256
256
|
<Providers extends PlatformProviderConfig[]>(spectrum: SpectrumLike<Providers>): HasProvider<Providers, Def["name"]> extends true ? PlatformInstance<Def> : never;
|
|
257
257
|
(space: Space): PlatformSpace<Def>;
|
|
258
258
|
(message: Message): PlatformMessage<Def>;
|