spectrum-ts 0.2.2 → 0.4.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-5XW4CAWS.js +165 -0
- package/dist/{chunk-LIRM7SBA.js → chunk-XEEDIGVK.js} +28 -5
- package/dist/index.d.ts +15 -4
- package/dist/index.js +25 -76
- package/dist/providers/imessage/index.d.ts +1 -1
- package/dist/providers/imessage/index.js +98 -35
- package/dist/providers/terminal/index.d.ts +3 -3
- package/dist/providers/terminal/index.js +4 -5
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +57 -98
- package/dist/{types-DPrSH21Q.d.ts → types-BdWMydUJ.d.ts} +14 -17
- package/package.json +7 -30
- package/dist/chunk-3TBRO2J7.js +0 -58
- package/src/index.ts +0 -38
- package/src/platform/define.ts +0 -308
- package/src/platform/types.ts +0 -442
- package/src/providers/imessage/auth.ts +0 -115
- package/src/providers/imessage/index.ts +0 -153
- package/src/providers/imessage/local.ts +0 -55
- package/src/providers/imessage/remote.ts +0 -157
- package/src/providers/imessage/types.ts +0 -31
- package/src/providers/terminal/index.ts +0 -66
- package/src/providers/whatsapp-business/index.ts +0 -77
- package/src/providers/whatsapp-business/messages.ts +0 -240
- package/src/providers/whatsapp-business/types.ts +0 -19
- package/src/spectrum.ts +0 -390
- package/src/types/content.ts +0 -85
- package/src/types/message.ts +0 -18
- package/src/types/space.ts +0 -10
- package/src/types/user.ts +0 -4
- package/src/utils/cloud.ts +0 -147
- package/src/utils/stream.ts +0 -71
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import type {
|
|
5
|
-
IMessageSDK,
|
|
6
|
-
Message as LocalIMessage,
|
|
7
|
-
} from "@photon-ai/imessage-kit";
|
|
8
|
-
import type { Content } from "../../types/content";
|
|
9
|
-
import { type ManagedStream, stream } from "../../utils/stream";
|
|
10
|
-
import type { IMessageMessage } from "./types";
|
|
11
|
-
|
|
12
|
-
const toSpace = (message: LocalIMessage): IMessageMessage["space"] => ({
|
|
13
|
-
id: `${message.isGroupChat ? "any;+;" : "any;-;"}${message.chatId}`,
|
|
14
|
-
type: message.isGroupChat ? "group" : "dm",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const toMessage = (message: LocalIMessage): IMessageMessage => ({
|
|
18
|
-
id: message.guid,
|
|
19
|
-
content: [{ type: "plain_text", text: message.text ?? "" }],
|
|
20
|
-
sender: { id: message.sender ?? "" },
|
|
21
|
-
space: toSpace(message),
|
|
22
|
-
timestamp: message.date ?? new Date(),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export const messages = (client: IMessageSDK): ManagedStream<IMessageMessage> =>
|
|
26
|
-
stream((emit) => {
|
|
27
|
-
client.startWatching({
|
|
28
|
-
onMessage: (message) => emit(toMessage(message)),
|
|
29
|
-
});
|
|
30
|
-
return () => client.stopWatching();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export const send = async (
|
|
34
|
-
client: IMessageSDK,
|
|
35
|
-
spaceId: string,
|
|
36
|
-
content: Content
|
|
37
|
-
) => {
|
|
38
|
-
switch (content.type) {
|
|
39
|
-
case "plain_text":
|
|
40
|
-
await client.send(spaceId, content.text);
|
|
41
|
-
break;
|
|
42
|
-
case "attachment": {
|
|
43
|
-
const tmp = join(tmpdir(), `spectrum-${Date.now()}-${content.name}`);
|
|
44
|
-
await writeFile(tmp, content.data);
|
|
45
|
-
try {
|
|
46
|
-
await client.send(spaceId, { files: [tmp] });
|
|
47
|
-
} finally {
|
|
48
|
-
await unlink(tmp).catch(() => {});
|
|
49
|
-
}
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
default:
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AdvancedIMessage,
|
|
3
|
-
chatGuid,
|
|
4
|
-
type MessageEvent,
|
|
5
|
-
messageGuid,
|
|
6
|
-
Reaction,
|
|
7
|
-
} from "@photon-ai/advanced-imessage";
|
|
8
|
-
import type { Content } from "../../types/content";
|
|
9
|
-
import { type ManagedStream, mergeStreams, stream } from "../../utils/stream";
|
|
10
|
-
import type { IMessageMessage } from "./types";
|
|
11
|
-
|
|
12
|
-
type ReceivedEvent = Extract<MessageEvent, { type: "message.received" }>;
|
|
13
|
-
|
|
14
|
-
const TAPBACK_NAMES: ReadonlySet<string> = new Set(
|
|
15
|
-
Object.values(Reaction).filter((r) => r !== "emoji" && r !== "sticker")
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const toMessage = (event: ReceivedEvent): IMessageMessage => ({
|
|
19
|
-
id: event.message.guid as string,
|
|
20
|
-
content: [{ type: "plain_text", text: event.message.text ?? "" }],
|
|
21
|
-
sender: { id: event.message.sender?.address ?? "" },
|
|
22
|
-
space: {
|
|
23
|
-
id: event.chatGuid,
|
|
24
|
-
type: event.chatGuid.includes(";+;") ? "group" : "dm",
|
|
25
|
-
},
|
|
26
|
-
timestamp: event.timestamp,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const clientStream = (
|
|
30
|
-
client: AdvancedIMessage
|
|
31
|
-
): ManagedStream<IMessageMessage> => {
|
|
32
|
-
const sub = client.messages.subscribe("message.received");
|
|
33
|
-
return stream<IMessageMessage>((emit, end) => {
|
|
34
|
-
(async () => {
|
|
35
|
-
try {
|
|
36
|
-
for await (const event of sub) {
|
|
37
|
-
emit(toMessage(event));
|
|
38
|
-
}
|
|
39
|
-
end();
|
|
40
|
-
} catch (e) {
|
|
41
|
-
end(e);
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
44
|
-
return () => sub.close();
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const messages = (
|
|
49
|
-
clients: AdvancedIMessage[]
|
|
50
|
-
): ManagedStream<IMessageMessage> => mergeStreams(clients.map(clientStream));
|
|
51
|
-
|
|
52
|
-
export const startTyping = async (
|
|
53
|
-
clients: AdvancedIMessage[],
|
|
54
|
-
spaceId: string
|
|
55
|
-
) => {
|
|
56
|
-
const remote = clients[0];
|
|
57
|
-
if (!remote) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
await remote.chats.startTyping(chatGuid(spaceId));
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const stopTyping = async (
|
|
64
|
-
clients: AdvancedIMessage[],
|
|
65
|
-
spaceId: string
|
|
66
|
-
) => {
|
|
67
|
-
const remote = clients[0];
|
|
68
|
-
if (!remote) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
await remote.chats.stopTyping(chatGuid(spaceId));
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const send = async (
|
|
75
|
-
clients: AdvancedIMessage[],
|
|
76
|
-
spaceId: string,
|
|
77
|
-
content: Content
|
|
78
|
-
) => {
|
|
79
|
-
const remote = clients[0];
|
|
80
|
-
if (!remote) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
switch (content.type) {
|
|
84
|
-
case "plain_text":
|
|
85
|
-
await remote.messages.send(chatGuid(spaceId), content.text);
|
|
86
|
-
break;
|
|
87
|
-
case "attachment": {
|
|
88
|
-
const attachment = await remote.attachments.upload({
|
|
89
|
-
data: content.data,
|
|
90
|
-
fileName: content.name,
|
|
91
|
-
mimeType: content.mimeType,
|
|
92
|
-
});
|
|
93
|
-
await remote.messages.send(chatGuid(spaceId), "", {
|
|
94
|
-
attachment: attachment.guid,
|
|
95
|
-
});
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
default:
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const replyToMessage = async (
|
|
104
|
-
clients: AdvancedIMessage[],
|
|
105
|
-
spaceId: string,
|
|
106
|
-
msgId: string,
|
|
107
|
-
content: Content
|
|
108
|
-
) => {
|
|
109
|
-
const remote = clients[0];
|
|
110
|
-
if (!remote) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const chat = chatGuid(spaceId);
|
|
115
|
-
const replyTo = messageGuid(msgId);
|
|
116
|
-
|
|
117
|
-
switch (content.type) {
|
|
118
|
-
case "plain_text":
|
|
119
|
-
await remote.messages.send(chat, content.text, { replyTo });
|
|
120
|
-
break;
|
|
121
|
-
case "attachment": {
|
|
122
|
-
const attachment = await remote.attachments.upload({
|
|
123
|
-
data: content.data,
|
|
124
|
-
fileName: content.name,
|
|
125
|
-
mimeType: content.mimeType,
|
|
126
|
-
});
|
|
127
|
-
await remote.messages.send(chat, "", {
|
|
128
|
-
attachment: attachment.guid,
|
|
129
|
-
replyTo,
|
|
130
|
-
});
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
default:
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export const reactToMessage = async (
|
|
139
|
-
clients: AdvancedIMessage[],
|
|
140
|
-
spaceId: string,
|
|
141
|
-
msgId: string,
|
|
142
|
-
reaction: string
|
|
143
|
-
) => {
|
|
144
|
-
const remote = clients[0];
|
|
145
|
-
if (!remote) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const chat = chatGuid(spaceId);
|
|
150
|
-
const msg = messageGuid(msgId);
|
|
151
|
-
|
|
152
|
-
if (TAPBACK_NAMES.has(reaction)) {
|
|
153
|
-
await remote.messages.react(chat, msg, reaction as Reaction);
|
|
154
|
-
} else {
|
|
155
|
-
await remote.messages.reactEmoji(chat, msg, reaction);
|
|
156
|
-
}
|
|
157
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { AdvancedIMessage } from "@photon-ai/advanced-imessage";
|
|
2
|
-
import { IMessageSDK } from "@photon-ai/imessage-kit";
|
|
3
|
-
import z from "zod";
|
|
4
|
-
import type { SchemaMessage } from "../../platform/types";
|
|
5
|
-
|
|
6
|
-
export type IMessageClient = IMessageSDK | AdvancedIMessage[];
|
|
7
|
-
|
|
8
|
-
export const isLocal = (client: IMessageClient): client is IMessageSDK =>
|
|
9
|
-
client instanceof IMessageSDK;
|
|
10
|
-
|
|
11
|
-
const clientEntry = z.object({ address: z.string(), token: z.string() });
|
|
12
|
-
|
|
13
|
-
export const configSchema = z.union([
|
|
14
|
-
z.object({ local: z.literal(true) }),
|
|
15
|
-
z.object({
|
|
16
|
-
local: z.literal(false).optional().default(false),
|
|
17
|
-
clients: clientEntry.or(z.array(clientEntry)).optional(),
|
|
18
|
-
}),
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
export const userSchema = z.object({});
|
|
22
|
-
|
|
23
|
-
export const spaceSchema = z.object({
|
|
24
|
-
id: z.string(),
|
|
25
|
-
type: z.enum(["dm", "group"]),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export type IMessageMessage = SchemaMessage<
|
|
29
|
-
typeof userSchema,
|
|
30
|
-
typeof spaceSchema
|
|
31
|
-
>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { definePlatform } from "../../platform/define";
|
|
4
|
-
|
|
5
|
-
export const terminal = definePlatform("terminal", {
|
|
6
|
-
config: z.object({}),
|
|
7
|
-
|
|
8
|
-
user: {
|
|
9
|
-
resolve: async ({ input }) => ({
|
|
10
|
-
id: input.userID,
|
|
11
|
-
}),
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
space: {
|
|
15
|
-
resolve: async () => ({
|
|
16
|
-
id: "terminal",
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
lifecycle: {
|
|
21
|
-
createClient: async () => {
|
|
22
|
-
const client = createInterface({
|
|
23
|
-
input: process.stdin,
|
|
24
|
-
output: process.stdout,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
client.on("SIGINT", () => {
|
|
28
|
-
client.close();
|
|
29
|
-
process.kill(process.pid, "SIGINT");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return client;
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
destroyClient: async ({ client }) => {
|
|
36
|
-
client.close();
|
|
37
|
-
process.stdin.unref();
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
events: {
|
|
42
|
-
async *messages({ client }) {
|
|
43
|
-
for await (const line of client) {
|
|
44
|
-
yield {
|
|
45
|
-
id: crypto.randomUUID(),
|
|
46
|
-
content: [{ type: "plain_text" as const, text: line }],
|
|
47
|
-
sender: { id: "terminal-user" },
|
|
48
|
-
space: { id: "terminal" },
|
|
49
|
-
timestamp: new Date(),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
actions: {
|
|
56
|
-
send: async ({ content }) => {
|
|
57
|
-
const outputs = content
|
|
58
|
-
.filter((c) => c.type === "plain_text")
|
|
59
|
-
.map((c) => c.text);
|
|
60
|
-
|
|
61
|
-
for (const output of outputs) {
|
|
62
|
-
console.log(output);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClient,
|
|
3
|
-
type WhatsAppClient,
|
|
4
|
-
} from "@photon-ai/whatsapp-business";
|
|
5
|
-
import { definePlatform } from "../../platform/define";
|
|
6
|
-
import { messages, reactToMessage, replyToMessage, send } from "./messages";
|
|
7
|
-
import { configSchema, spaceSchema } from "./types";
|
|
8
|
-
|
|
9
|
-
export const whatsappBusiness = definePlatform("WhatsApp Business", {
|
|
10
|
-
config: configSchema,
|
|
11
|
-
|
|
12
|
-
user: {
|
|
13
|
-
resolve: async ({ input }) => ({ id: input.userID }),
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
space: {
|
|
17
|
-
schema: spaceSchema,
|
|
18
|
-
resolve: async ({ input }) => {
|
|
19
|
-
if (input.users.length === 0) {
|
|
20
|
-
throw new Error("WhatsApp space creation requires at least one user");
|
|
21
|
-
}
|
|
22
|
-
if (input.users.length > 1) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
"WhatsApp Business API only supports 1:1 conversations"
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
const user = input.users[0];
|
|
28
|
-
if (!user) {
|
|
29
|
-
throw new Error("WhatsApp space creation requires a user");
|
|
30
|
-
}
|
|
31
|
-
return { id: user.id };
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
lifecycle: {
|
|
36
|
-
createClient: async ({ config }): Promise<WhatsAppClient> => {
|
|
37
|
-
return createClient({
|
|
38
|
-
accessToken: config.accessToken,
|
|
39
|
-
phoneNumberId: config.phoneNumberId,
|
|
40
|
-
appSecret: config.appSecret ?? "",
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
destroyClient: async ({ client }: { client: WhatsAppClient }) => {
|
|
45
|
-
await client.close();
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
events: {
|
|
50
|
-
messages: ({ client }) => messages(client as WhatsAppClient),
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
actions: {
|
|
54
|
-
send: async ({ space, content, client }) => {
|
|
55
|
-
const wa = client as WhatsAppClient;
|
|
56
|
-
for (const item of content) {
|
|
57
|
-
await send(wa, space.id, item);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
reactToMessage: async ({ space, messageId, reaction, client }) => {
|
|
62
|
-
await reactToMessage(
|
|
63
|
-
client as WhatsAppClient,
|
|
64
|
-
space.id,
|
|
65
|
-
messageId,
|
|
66
|
-
reaction
|
|
67
|
-
);
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
replyToMessage: async ({ space, messageId, content, client }) => {
|
|
71
|
-
const wa = client as WhatsAppClient;
|
|
72
|
-
for (const item of content) {
|
|
73
|
-
await replyToMessage(wa, space.id, messageId, item);
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
});
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
InboundMessage,
|
|
3
|
-
WhatsAppClient,
|
|
4
|
-
} from "@photon-ai/whatsapp-business";
|
|
5
|
-
import type { Content } from "../../types/content";
|
|
6
|
-
import { type ManagedStream, stream } from "../../utils/stream";
|
|
7
|
-
import type { WhatsAppMessage } from "./types";
|
|
8
|
-
|
|
9
|
-
const toMessage = async (
|
|
10
|
-
client: WhatsAppClient,
|
|
11
|
-
msg: InboundMessage
|
|
12
|
-
): Promise<WhatsAppMessage> => {
|
|
13
|
-
const content = await mapContent(client, msg.content);
|
|
14
|
-
return {
|
|
15
|
-
id: msg.id,
|
|
16
|
-
content,
|
|
17
|
-
sender: { id: msg.from },
|
|
18
|
-
space: { id: msg.from },
|
|
19
|
-
timestamp: msg.timestamp,
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const mapContent = async (
|
|
24
|
-
client: WhatsAppClient,
|
|
25
|
-
content: InboundMessage["content"]
|
|
26
|
-
): Promise<Content[]> => {
|
|
27
|
-
switch (content.type) {
|
|
28
|
-
case "text":
|
|
29
|
-
return [{ type: "plain_text", text: content.body }];
|
|
30
|
-
case "image":
|
|
31
|
-
case "video":
|
|
32
|
-
case "audio":
|
|
33
|
-
case "document":
|
|
34
|
-
return [await downloadMedia(client, content.media)];
|
|
35
|
-
case "sticker":
|
|
36
|
-
return [
|
|
37
|
-
{
|
|
38
|
-
type: "custom",
|
|
39
|
-
raw: { whatsapp_type: "sticker", ...content.sticker },
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
case "location":
|
|
43
|
-
return [
|
|
44
|
-
{
|
|
45
|
-
type: "custom",
|
|
46
|
-
raw: { whatsapp_type: "location", ...content.location },
|
|
47
|
-
},
|
|
48
|
-
];
|
|
49
|
-
case "contacts":
|
|
50
|
-
return [
|
|
51
|
-
{
|
|
52
|
-
type: "custom",
|
|
53
|
-
raw: { whatsapp_type: "contacts", contacts: content.contacts },
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
case "reaction":
|
|
57
|
-
return [
|
|
58
|
-
{
|
|
59
|
-
type: "custom",
|
|
60
|
-
raw: { whatsapp_type: "reaction", ...content.reaction },
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
case "interactive":
|
|
64
|
-
return [
|
|
65
|
-
{
|
|
66
|
-
type: "custom",
|
|
67
|
-
raw: { whatsapp_type: "interactive", ...content.interactive },
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
case "button":
|
|
71
|
-
return [
|
|
72
|
-
{
|
|
73
|
-
type: "custom",
|
|
74
|
-
raw: { whatsapp_type: "button", ...content.button },
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
case "order":
|
|
78
|
-
return [
|
|
79
|
-
{ type: "custom", raw: { whatsapp_type: "order", ...content.order } },
|
|
80
|
-
];
|
|
81
|
-
case "system":
|
|
82
|
-
return [
|
|
83
|
-
{
|
|
84
|
-
type: "custom",
|
|
85
|
-
raw: { whatsapp_type: "system", ...content.system },
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
default:
|
|
89
|
-
return [{ type: "custom", raw: { whatsapp_type: "unknown" } }];
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const downloadMedia = async (
|
|
94
|
-
client: WhatsAppClient,
|
|
95
|
-
media: { id: string; mimeType: string; filename?: string }
|
|
96
|
-
): Promise<Content> => {
|
|
97
|
-
try {
|
|
98
|
-
const { url } = await client.media.getUrl(media.id);
|
|
99
|
-
const response = await fetch(url);
|
|
100
|
-
if (!response.ok) {
|
|
101
|
-
throw new Error(`Media download failed: ${response.status}`);
|
|
102
|
-
}
|
|
103
|
-
const data = Buffer.from(await response.arrayBuffer());
|
|
104
|
-
return {
|
|
105
|
-
type: "attachment",
|
|
106
|
-
data,
|
|
107
|
-
mimeType: media.mimeType,
|
|
108
|
-
name: media.filename ?? `media-${media.id}`,
|
|
109
|
-
};
|
|
110
|
-
} catch {
|
|
111
|
-
return {
|
|
112
|
-
type: "custom",
|
|
113
|
-
raw: {
|
|
114
|
-
whatsapp_type: "media_error",
|
|
115
|
-
mediaId: media.id,
|
|
116
|
-
mimeType: media.mimeType,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const mimeToMediaType = (
|
|
123
|
-
mimeType: string
|
|
124
|
-
): "image" | "video" | "audio" | "document" => {
|
|
125
|
-
if (mimeType.startsWith("image/")) {
|
|
126
|
-
return "image";
|
|
127
|
-
}
|
|
128
|
-
if (mimeType.startsWith("video/")) {
|
|
129
|
-
return "video";
|
|
130
|
-
}
|
|
131
|
-
if (mimeType.startsWith("audio/")) {
|
|
132
|
-
return "audio";
|
|
133
|
-
}
|
|
134
|
-
return "document";
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
export const messages = (
|
|
138
|
-
client: WhatsAppClient
|
|
139
|
-
): ManagedStream<WhatsAppMessage> => {
|
|
140
|
-
const eventStream = client.events
|
|
141
|
-
.subscribe()
|
|
142
|
-
.filter(
|
|
143
|
-
(e): e is Extract<typeof e, { type: "message" }> => e.type === "message"
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
return stream<WhatsAppMessage>((emit, end) => {
|
|
147
|
-
(async () => {
|
|
148
|
-
try {
|
|
149
|
-
for await (const event of eventStream) {
|
|
150
|
-
const msg = await toMessage(client, event.message);
|
|
151
|
-
emit(msg);
|
|
152
|
-
}
|
|
153
|
-
end();
|
|
154
|
-
} catch (e) {
|
|
155
|
-
end(e);
|
|
156
|
-
}
|
|
157
|
-
})();
|
|
158
|
-
return () => eventStream.close();
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
export const send = async (
|
|
163
|
-
client: WhatsAppClient,
|
|
164
|
-
spaceId: string,
|
|
165
|
-
content: Content
|
|
166
|
-
): Promise<void> => {
|
|
167
|
-
switch (content.type) {
|
|
168
|
-
case "plain_text":
|
|
169
|
-
await client.messages.send({ to: spaceId, text: content.text });
|
|
170
|
-
break;
|
|
171
|
-
case "attachment": {
|
|
172
|
-
const { mediaId } = await client.media.upload({
|
|
173
|
-
file: content.data,
|
|
174
|
-
mimeType: content.mimeType,
|
|
175
|
-
filename: content.name,
|
|
176
|
-
});
|
|
177
|
-
const mediaType = mimeToMediaType(content.mimeType);
|
|
178
|
-
const mediaPayload =
|
|
179
|
-
mediaType === "document"
|
|
180
|
-
? { id: mediaId, filename: content.name }
|
|
181
|
-
: { id: mediaId };
|
|
182
|
-
await client.messages.send({
|
|
183
|
-
to: spaceId,
|
|
184
|
-
[mediaType]: mediaPayload,
|
|
185
|
-
} as Parameters<typeof client.messages.send>[0]);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
default:
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
export const reactToMessage = async (
|
|
194
|
-
client: WhatsAppClient,
|
|
195
|
-
spaceId: string,
|
|
196
|
-
messageId: string,
|
|
197
|
-
reaction: string
|
|
198
|
-
): Promise<void> => {
|
|
199
|
-
await client.messages.send({
|
|
200
|
-
to: spaceId,
|
|
201
|
-
reaction: { messageId, emoji: reaction },
|
|
202
|
-
});
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
export const replyToMessage = async (
|
|
206
|
-
client: WhatsAppClient,
|
|
207
|
-
spaceId: string,
|
|
208
|
-
messageId: string,
|
|
209
|
-
content: Content
|
|
210
|
-
): Promise<void> => {
|
|
211
|
-
switch (content.type) {
|
|
212
|
-
case "plain_text":
|
|
213
|
-
await client.messages.send({
|
|
214
|
-
to: spaceId,
|
|
215
|
-
replyTo: messageId,
|
|
216
|
-
text: content.text,
|
|
217
|
-
});
|
|
218
|
-
break;
|
|
219
|
-
case "attachment": {
|
|
220
|
-
const { mediaId } = await client.media.upload({
|
|
221
|
-
file: content.data,
|
|
222
|
-
mimeType: content.mimeType,
|
|
223
|
-
filename: content.name,
|
|
224
|
-
});
|
|
225
|
-
const mediaType = mimeToMediaType(content.mimeType);
|
|
226
|
-
const mediaPayload =
|
|
227
|
-
mediaType === "document"
|
|
228
|
-
? { id: mediaId, filename: content.name }
|
|
229
|
-
: { id: mediaId };
|
|
230
|
-
await client.messages.send({
|
|
231
|
-
to: spaceId,
|
|
232
|
-
replyTo: messageId,
|
|
233
|
-
[mediaType]: mediaPayload,
|
|
234
|
-
} as Parameters<typeof client.messages.send>[0]);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
default:
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import z from "zod";
|
|
2
|
-
import type { SchemaMessage } from "../../platform/types";
|
|
3
|
-
|
|
4
|
-
export const configSchema = z.object({
|
|
5
|
-
accessToken: z.string().min(1),
|
|
6
|
-
phoneNumberId: z.string().min(1),
|
|
7
|
-
appSecret: z.string().optional(),
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export const userSchema = z.object({});
|
|
11
|
-
|
|
12
|
-
export const spaceSchema = z.object({
|
|
13
|
-
id: z.string(),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export type WhatsAppMessage = SchemaMessage<
|
|
17
|
-
typeof userSchema,
|
|
18
|
-
typeof spaceSchema
|
|
19
|
-
>;
|