@pocketping/sdk-node 1.3.0 → 1.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/README.md +13 -0
- package/dist/index.cjs +145 -14
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +145 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -209,6 +209,12 @@ pp.addBridge(DiscordBridge.withBot({ ... }));
|
|
|
209
209
|
pp.addBridge(SlackBridge.withWebhook({ ... }));
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
### Reply Behavior
|
|
213
|
+
|
|
214
|
+
- **Telegram:** uses native replies when `replyTo` is set and the Telegram message ID is available.
|
|
215
|
+
- **Discord:** uses native replies via `message_reference` when the Discord message ID is available.
|
|
216
|
+
- **Slack:** renders a quoted block (left bar) in the thread.
|
|
217
|
+
|
|
212
218
|
## Architecture Options
|
|
213
219
|
|
|
214
220
|
### 1. Embedded Mode with Built-in Bridges (Simple)
|
|
@@ -255,6 +261,7 @@ const webhookHandler = new WebhookHandler({
|
|
|
255
261
|
telegramBotToken: process.env.TELEGRAM_BOT_TOKEN,
|
|
256
262
|
slackBotToken: process.env.SLACK_BOT_TOKEN,
|
|
257
263
|
discordBotToken: process.env.DISCORD_BOT_TOKEN,
|
|
264
|
+
allowedBotIds: process.env.BRIDGE_TEST_BOT_IDS?.split(',').map((id) => id.trim()).filter(Boolean),
|
|
258
265
|
onOperatorMessage: async (sessionId, content, operatorName, source, attachments) => {
|
|
259
266
|
console.log(`Message from ${operatorName} via ${source}: ${content}`);
|
|
260
267
|
|
|
@@ -267,6 +274,12 @@ const webhookHandler = new WebhookHandler({
|
|
|
267
274
|
// att.data contains the file bytes
|
|
268
275
|
}
|
|
269
276
|
},
|
|
277
|
+
onOperatorMessageEdit: async (sessionId, bridgeMessageId, content, source) => {
|
|
278
|
+
console.log(`Edited message ${bridgeMessageId} via ${source}: ${content}`);
|
|
279
|
+
},
|
|
280
|
+
onOperatorMessageDelete: async (sessionId, bridgeMessageId, source) => {
|
|
281
|
+
console.log(`Deleted message ${bridgeMessageId} via ${source}`);
|
|
282
|
+
},
|
|
270
283
|
});
|
|
271
284
|
|
|
272
285
|
// Mount webhook routes
|
package/dist/index.cjs
CHANGED
|
@@ -1212,6 +1212,35 @@ var WebhookHandler = class {
|
|
|
1212
1212
|
try {
|
|
1213
1213
|
const body = await this.parseBody(req);
|
|
1214
1214
|
const update = body;
|
|
1215
|
+
if (update.edited_message) {
|
|
1216
|
+
const msg = update.edited_message;
|
|
1217
|
+
if (msg.text?.startsWith("/")) {
|
|
1218
|
+
this.writeOK(res);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const text = msg.text ?? msg.caption ?? "";
|
|
1222
|
+
if (!text) {
|
|
1223
|
+
this.writeOK(res);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const topicId = msg.message_thread_id;
|
|
1227
|
+
if (!topicId) {
|
|
1228
|
+
this.writeOK(res);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (this.config.onOperatorMessageEdit) {
|
|
1232
|
+
const editedAt = msg.edit_date ? new Date(msg.edit_date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1233
|
+
await this.config.onOperatorMessageEdit(
|
|
1234
|
+
String(topicId),
|
|
1235
|
+
msg.message_id,
|
|
1236
|
+
text,
|
|
1237
|
+
"telegram",
|
|
1238
|
+
editedAt
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
this.writeOK(res);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1215
1244
|
if (update.message) {
|
|
1216
1245
|
const msg = update.message;
|
|
1217
1246
|
if (msg.text?.startsWith("/")) {
|
|
@@ -1267,6 +1296,7 @@ var WebhookHandler = class {
|
|
|
1267
1296
|
return;
|
|
1268
1297
|
}
|
|
1269
1298
|
const operatorName = msg.from?.first_name ?? "Operator";
|
|
1299
|
+
const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
|
|
1270
1300
|
const attachments = [];
|
|
1271
1301
|
if (media) {
|
|
1272
1302
|
const data = await this.downloadTelegramFile(media.fileId);
|
|
@@ -1285,7 +1315,9 @@ var WebhookHandler = class {
|
|
|
1285
1315
|
text,
|
|
1286
1316
|
operatorName,
|
|
1287
1317
|
"telegram",
|
|
1288
|
-
attachments
|
|
1318
|
+
attachments,
|
|
1319
|
+
replyToBridgeMessageId,
|
|
1320
|
+
msg.message_id
|
|
1289
1321
|
);
|
|
1290
1322
|
}
|
|
1291
1323
|
this.writeOK(res);
|
|
@@ -1316,7 +1348,56 @@ var WebhookHandler = class {
|
|
|
1316
1348
|
}
|
|
1317
1349
|
if (payload.type === "event_callback" && payload.event) {
|
|
1318
1350
|
const event = payload.event;
|
|
1319
|
-
const
|
|
1351
|
+
const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
|
|
1352
|
+
if (event.subtype === "message_changed") {
|
|
1353
|
+
if (!this.config.onOperatorMessageEdit) {
|
|
1354
|
+
this.writeOK(res);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
|
|
1358
|
+
if (botId && !isAllowedBot(botId)) {
|
|
1359
|
+
this.writeOK(res);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
|
|
1363
|
+
const messageTs = event.message?.ts ?? event.previous_message?.ts;
|
|
1364
|
+
const text = event.message?.text ?? "";
|
|
1365
|
+
if (threadTs && messageTs) {
|
|
1366
|
+
await this.config.onOperatorMessageEdit(
|
|
1367
|
+
threadTs,
|
|
1368
|
+
messageTs,
|
|
1369
|
+
text,
|
|
1370
|
+
"slack",
|
|
1371
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
this.writeOK(res);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (event.subtype === "message_deleted") {
|
|
1378
|
+
if (!this.config.onOperatorMessageDelete) {
|
|
1379
|
+
this.writeOK(res);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const botId = event.previous_message?.bot_id ?? event.bot_id;
|
|
1383
|
+
if (botId && !isAllowedBot(botId)) {
|
|
1384
|
+
this.writeOK(res);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
const threadTs = event.previous_message?.thread_ts;
|
|
1388
|
+
const messageTs = event.deleted_ts ?? event.previous_message?.ts;
|
|
1389
|
+
if (threadTs && messageTs) {
|
|
1390
|
+
await this.config.onOperatorMessageDelete(
|
|
1391
|
+
threadTs,
|
|
1392
|
+
messageTs,
|
|
1393
|
+
"slack",
|
|
1394
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
this.writeOK(res);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
|
|
1320
1401
|
const hasFiles = event.files && event.files.length > 0;
|
|
1321
1402
|
if (hasContent && (event.text || hasFiles)) {
|
|
1322
1403
|
const threadTs = event.thread_ts;
|
|
@@ -1346,7 +1427,9 @@ var WebhookHandler = class {
|
|
|
1346
1427
|
text,
|
|
1347
1428
|
operatorName,
|
|
1348
1429
|
"slack",
|
|
1349
|
-
attachments
|
|
1430
|
+
attachments,
|
|
1431
|
+
null,
|
|
1432
|
+
event.ts ?? null
|
|
1350
1433
|
);
|
|
1351
1434
|
}
|
|
1352
1435
|
}
|
|
@@ -1384,7 +1467,8 @@ var WebhookHandler = class {
|
|
|
1384
1467
|
content,
|
|
1385
1468
|
operatorName,
|
|
1386
1469
|
"discord",
|
|
1387
|
-
[]
|
|
1470
|
+
[],
|
|
1471
|
+
null
|
|
1388
1472
|
);
|
|
1389
1473
|
res.setHeader("Content-Type", "application/json");
|
|
1390
1474
|
res.end(
|
|
@@ -1496,7 +1580,8 @@ var TelegramBridge = class {
|
|
|
1496
1580
|
/**
|
|
1497
1581
|
* Initialize the bridge (optional setup)
|
|
1498
1582
|
*/
|
|
1499
|
-
async init(
|
|
1583
|
+
async init(pocketping) {
|
|
1584
|
+
this.pocketping = pocketping;
|
|
1500
1585
|
try {
|
|
1501
1586
|
const response = await fetch(`${this.baseUrl}/getMe`);
|
|
1502
1587
|
const data = await response.json();
|
|
@@ -1525,8 +1610,18 @@ var TelegramBridge = class {
|
|
|
1525
1610
|
*/
|
|
1526
1611
|
async onVisitorMessage(message, session) {
|
|
1527
1612
|
const text = this.formatVisitorMessage(session.visitorId, message.content);
|
|
1613
|
+
let replyToMessageId;
|
|
1614
|
+
if (message.replyTo) {
|
|
1615
|
+
const storage = this.pocketping?.getStorage();
|
|
1616
|
+
if (storage?.getBridgeMessageIds) {
|
|
1617
|
+
const ids = await storage.getBridgeMessageIds(message.replyTo);
|
|
1618
|
+
if (ids?.telegramMessageId) {
|
|
1619
|
+
replyToMessageId = ids.telegramMessageId;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1528
1623
|
try {
|
|
1529
|
-
const messageId = await this.sendMessage(text);
|
|
1624
|
+
const messageId = await this.sendMessage(text, replyToMessageId);
|
|
1530
1625
|
return { messageId };
|
|
1531
1626
|
} catch (error) {
|
|
1532
1627
|
console.error("[TelegramBridge] Failed to send visitor message:", error);
|
|
@@ -1663,7 +1758,7 @@ ${dataStr}
|
|
|
1663
1758
|
/**
|
|
1664
1759
|
* Send a message to the Telegram chat
|
|
1665
1760
|
*/
|
|
1666
|
-
async sendMessage(text) {
|
|
1761
|
+
async sendMessage(text, replyToMessageId) {
|
|
1667
1762
|
const response = await fetch(`${this.baseUrl}/sendMessage`, {
|
|
1668
1763
|
method: "POST",
|
|
1669
1764
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1671,7 +1766,8 @@ ${dataStr}
|
|
|
1671
1766
|
chat_id: this.chatId,
|
|
1672
1767
|
text,
|
|
1673
1768
|
parse_mode: this.parseMode,
|
|
1674
|
-
disable_notification: this.disableNotification
|
|
1769
|
+
disable_notification: this.disableNotification,
|
|
1770
|
+
...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
|
|
1675
1771
|
})
|
|
1676
1772
|
});
|
|
1677
1773
|
const data = await response.json();
|
|
@@ -1768,7 +1864,8 @@ var DiscordBridge = class _DiscordBridge {
|
|
|
1768
1864
|
/**
|
|
1769
1865
|
* Initialize the bridge (optional setup)
|
|
1770
1866
|
*/
|
|
1771
|
-
async init(
|
|
1867
|
+
async init(pocketping) {
|
|
1868
|
+
this.pocketping = pocketping;
|
|
1772
1869
|
if (this.mode === "bot" && this.botToken) {
|
|
1773
1870
|
try {
|
|
1774
1871
|
const response = await fetch("https://discord.com/api/v10/users/@me", {
|
|
@@ -1818,8 +1915,18 @@ var DiscordBridge = class _DiscordBridge {
|
|
|
1818
1915
|
// Green
|
|
1819
1916
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1820
1917
|
};
|
|
1918
|
+
let replyToMessageId;
|
|
1919
|
+
if (message.replyTo) {
|
|
1920
|
+
const storage = this.pocketping?.getStorage();
|
|
1921
|
+
if (storage?.getBridgeMessageIds) {
|
|
1922
|
+
const ids = await storage.getBridgeMessageIds(message.replyTo);
|
|
1923
|
+
if (ids?.discordMessageId) {
|
|
1924
|
+
replyToMessageId = ids.discordMessageId;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1821
1928
|
try {
|
|
1822
|
-
const messageId = await this.sendEmbed(embed);
|
|
1929
|
+
const messageId = await this.sendEmbed(embed, replyToMessageId);
|
|
1823
1930
|
return { messageId };
|
|
1824
1931
|
} catch (error) {
|
|
1825
1932
|
console.error("[DiscordBridge] Failed to send visitor message:", error);
|
|
@@ -2009,7 +2116,7 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
2009
2116
|
/**
|
|
2010
2117
|
* Send an embed to Discord
|
|
2011
2118
|
*/
|
|
2012
|
-
async sendEmbed(embed) {
|
|
2119
|
+
async sendEmbed(embed, replyToMessageId) {
|
|
2013
2120
|
const body = {
|
|
2014
2121
|
embeds: [embed]
|
|
2015
2122
|
};
|
|
@@ -2020,6 +2127,9 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
2020
2127
|
body.avatar_url = this.avatarUrl;
|
|
2021
2128
|
}
|
|
2022
2129
|
if (this.mode === "webhook" && this.webhookUrl) {
|
|
2130
|
+
if (replyToMessageId) {
|
|
2131
|
+
body.message_reference = { message_id: replyToMessageId };
|
|
2132
|
+
}
|
|
2023
2133
|
const response = await fetch(`${this.webhookUrl}?wait=true`, {
|
|
2024
2134
|
method: "POST",
|
|
2025
2135
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2032,6 +2142,9 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
2032
2142
|
const data = await response.json();
|
|
2033
2143
|
return data.id;
|
|
2034
2144
|
} else if (this.mode === "bot" && this.channelId) {
|
|
2145
|
+
if (replyToMessageId) {
|
|
2146
|
+
body.message_reference = { message_id: replyToMessageId };
|
|
2147
|
+
}
|
|
2035
2148
|
const response = await fetch(
|
|
2036
2149
|
`https://discord.com/api/v10/channels/${this.channelId}/messages`,
|
|
2037
2150
|
{
|
|
@@ -2094,7 +2207,8 @@ var SlackBridge = class _SlackBridge {
|
|
|
2094
2207
|
/**
|
|
2095
2208
|
* Initialize the bridge (optional setup)
|
|
2096
2209
|
*/
|
|
2097
|
-
async init(
|
|
2210
|
+
async init(pocketping) {
|
|
2211
|
+
this.pocketping = pocketping;
|
|
2098
2212
|
if (this.mode === "bot" && this.botToken) {
|
|
2099
2213
|
try {
|
|
2100
2214
|
const response = await fetch("https://slack.com/api/auth.test", {
|
|
@@ -2154,7 +2268,24 @@ ${url}`
|
|
|
2154
2268
|
* Returns the Slack message timestamp for edit/delete sync.
|
|
2155
2269
|
*/
|
|
2156
2270
|
async onVisitorMessage(message, session) {
|
|
2157
|
-
const blocks = [
|
|
2271
|
+
const blocks = [];
|
|
2272
|
+
if (message.replyTo && this.pocketping?.getStorage().getMessage) {
|
|
2273
|
+
const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
|
|
2274
|
+
if (replyTarget) {
|
|
2275
|
+
const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
|
|
2276
|
+
const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
|
|
2277
|
+
const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
|
|
2278
|
+
const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
|
|
2279
|
+
blocks.push({
|
|
2280
|
+
type: "section",
|
|
2281
|
+
text: {
|
|
2282
|
+
type: "mrkdwn",
|
|
2283
|
+
text: quoted
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
blocks.push(
|
|
2158
2289
|
{
|
|
2159
2290
|
type: "section",
|
|
2160
2291
|
text: {
|
|
@@ -2172,7 +2303,7 @@ ${this.escapeSlack(message.content)}`
|
|
|
2172
2303
|
}
|
|
2173
2304
|
]
|
|
2174
2305
|
}
|
|
2175
|
-
|
|
2306
|
+
);
|
|
2176
2307
|
try {
|
|
2177
2308
|
const messageId = await this.sendBlocks(blocks);
|
|
2178
2309
|
return { messageId };
|
package/dist/index.d.cts
CHANGED
|
@@ -594,13 +594,18 @@ interface OperatorAttachment {
|
|
|
594
594
|
bridgeFileId?: string;
|
|
595
595
|
}
|
|
596
596
|
/** Callback when operator sends a message from a bridge */
|
|
597
|
-
type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[]) => void | Promise<void>;
|
|
597
|
+
type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[], replyToBridgeMessageId?: number | null, bridgeMessageId?: number | string | null) => void | Promise<void>;
|
|
598
|
+
type OperatorMessageEditCallback = (sessionId: string, bridgeMessageId: number | string, content: string, sourceBridge: 'telegram' | 'discord' | 'slack', editedAt?: string) => void | Promise<void>;
|
|
599
|
+
type OperatorMessageDeleteCallback = (sessionId: string, bridgeMessageId: number | string, sourceBridge: 'telegram' | 'discord' | 'slack', deletedAt?: string) => void | Promise<void>;
|
|
598
600
|
/** Webhook handler configuration */
|
|
599
601
|
interface WebhookConfig {
|
|
600
602
|
telegramBotToken?: string;
|
|
601
603
|
slackBotToken?: string;
|
|
602
604
|
discordBotToken?: string;
|
|
605
|
+
allowedBotIds?: string[];
|
|
603
606
|
onOperatorMessage: OperatorMessageCallback;
|
|
607
|
+
onOperatorMessageEdit?: OperatorMessageEditCallback;
|
|
608
|
+
onOperatorMessageDelete?: OperatorMessageDeleteCallback;
|
|
604
609
|
}
|
|
605
610
|
declare class WebhookHandler {
|
|
606
611
|
private config;
|
|
@@ -655,6 +660,7 @@ interface TelegramBridgeOptions {
|
|
|
655
660
|
*/
|
|
656
661
|
declare class TelegramBridge implements Bridge {
|
|
657
662
|
readonly name = "telegram";
|
|
663
|
+
private pocketping?;
|
|
658
664
|
private readonly botToken;
|
|
659
665
|
private readonly chatId;
|
|
660
666
|
private readonly parseMode;
|
|
@@ -664,7 +670,7 @@ declare class TelegramBridge implements Bridge {
|
|
|
664
670
|
/**
|
|
665
671
|
* Initialize the bridge (optional setup)
|
|
666
672
|
*/
|
|
667
|
-
init(
|
|
673
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
668
674
|
/**
|
|
669
675
|
* Called when a new chat session is created
|
|
670
676
|
*/
|
|
@@ -770,6 +776,7 @@ interface DiscordBotOptions {
|
|
|
770
776
|
*/
|
|
771
777
|
declare class DiscordBridge implements Bridge {
|
|
772
778
|
readonly name = "discord";
|
|
779
|
+
private pocketping?;
|
|
773
780
|
private readonly mode;
|
|
774
781
|
private readonly webhookUrl?;
|
|
775
782
|
private readonly botToken?;
|
|
@@ -788,7 +795,7 @@ declare class DiscordBridge implements Bridge {
|
|
|
788
795
|
/**
|
|
789
796
|
* Initialize the bridge (optional setup)
|
|
790
797
|
*/
|
|
791
|
-
init(
|
|
798
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
792
799
|
/**
|
|
793
800
|
* Called when a new chat session is created
|
|
794
801
|
*/
|
|
@@ -878,6 +885,7 @@ interface SlackBotOptions {
|
|
|
878
885
|
*/
|
|
879
886
|
declare class SlackBridge implements Bridge {
|
|
880
887
|
readonly name = "slack";
|
|
888
|
+
private pocketping?;
|
|
881
889
|
private readonly mode;
|
|
882
890
|
private readonly webhookUrl?;
|
|
883
891
|
private readonly botToken?;
|
|
@@ -897,7 +905,7 @@ declare class SlackBridge implements Bridge {
|
|
|
897
905
|
/**
|
|
898
906
|
* Initialize the bridge (optional setup)
|
|
899
907
|
*/
|
|
900
|
-
init(
|
|
908
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
901
909
|
/**
|
|
902
910
|
* Called when a new chat session is created
|
|
903
911
|
*/
|
|
@@ -946,4 +954,4 @@ declare class SlackBridge implements Bridge {
|
|
|
946
954
|
private escapeSlack;
|
|
947
955
|
}
|
|
948
956
|
|
|
949
|
-
export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
|
|
957
|
+
export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, type OperatorMessageDeleteCallback, type OperatorMessageEditCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
|
package/dist/index.d.ts
CHANGED
|
@@ -594,13 +594,18 @@ interface OperatorAttachment {
|
|
|
594
594
|
bridgeFileId?: string;
|
|
595
595
|
}
|
|
596
596
|
/** Callback when operator sends a message from a bridge */
|
|
597
|
-
type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[]) => void | Promise<void>;
|
|
597
|
+
type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[], replyToBridgeMessageId?: number | null, bridgeMessageId?: number | string | null) => void | Promise<void>;
|
|
598
|
+
type OperatorMessageEditCallback = (sessionId: string, bridgeMessageId: number | string, content: string, sourceBridge: 'telegram' | 'discord' | 'slack', editedAt?: string) => void | Promise<void>;
|
|
599
|
+
type OperatorMessageDeleteCallback = (sessionId: string, bridgeMessageId: number | string, sourceBridge: 'telegram' | 'discord' | 'slack', deletedAt?: string) => void | Promise<void>;
|
|
598
600
|
/** Webhook handler configuration */
|
|
599
601
|
interface WebhookConfig {
|
|
600
602
|
telegramBotToken?: string;
|
|
601
603
|
slackBotToken?: string;
|
|
602
604
|
discordBotToken?: string;
|
|
605
|
+
allowedBotIds?: string[];
|
|
603
606
|
onOperatorMessage: OperatorMessageCallback;
|
|
607
|
+
onOperatorMessageEdit?: OperatorMessageEditCallback;
|
|
608
|
+
onOperatorMessageDelete?: OperatorMessageDeleteCallback;
|
|
604
609
|
}
|
|
605
610
|
declare class WebhookHandler {
|
|
606
611
|
private config;
|
|
@@ -655,6 +660,7 @@ interface TelegramBridgeOptions {
|
|
|
655
660
|
*/
|
|
656
661
|
declare class TelegramBridge implements Bridge {
|
|
657
662
|
readonly name = "telegram";
|
|
663
|
+
private pocketping?;
|
|
658
664
|
private readonly botToken;
|
|
659
665
|
private readonly chatId;
|
|
660
666
|
private readonly parseMode;
|
|
@@ -664,7 +670,7 @@ declare class TelegramBridge implements Bridge {
|
|
|
664
670
|
/**
|
|
665
671
|
* Initialize the bridge (optional setup)
|
|
666
672
|
*/
|
|
667
|
-
init(
|
|
673
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
668
674
|
/**
|
|
669
675
|
* Called when a new chat session is created
|
|
670
676
|
*/
|
|
@@ -770,6 +776,7 @@ interface DiscordBotOptions {
|
|
|
770
776
|
*/
|
|
771
777
|
declare class DiscordBridge implements Bridge {
|
|
772
778
|
readonly name = "discord";
|
|
779
|
+
private pocketping?;
|
|
773
780
|
private readonly mode;
|
|
774
781
|
private readonly webhookUrl?;
|
|
775
782
|
private readonly botToken?;
|
|
@@ -788,7 +795,7 @@ declare class DiscordBridge implements Bridge {
|
|
|
788
795
|
/**
|
|
789
796
|
* Initialize the bridge (optional setup)
|
|
790
797
|
*/
|
|
791
|
-
init(
|
|
798
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
792
799
|
/**
|
|
793
800
|
* Called when a new chat session is created
|
|
794
801
|
*/
|
|
@@ -878,6 +885,7 @@ interface SlackBotOptions {
|
|
|
878
885
|
*/
|
|
879
886
|
declare class SlackBridge implements Bridge {
|
|
880
887
|
readonly name = "slack";
|
|
888
|
+
private pocketping?;
|
|
881
889
|
private readonly mode;
|
|
882
890
|
private readonly webhookUrl?;
|
|
883
891
|
private readonly botToken?;
|
|
@@ -897,7 +905,7 @@ declare class SlackBridge implements Bridge {
|
|
|
897
905
|
/**
|
|
898
906
|
* Initialize the bridge (optional setup)
|
|
899
907
|
*/
|
|
900
|
-
init(
|
|
908
|
+
init(pocketping: PocketPing): Promise<void>;
|
|
901
909
|
/**
|
|
902
910
|
* Called when a new chat session is created
|
|
903
911
|
*/
|
|
@@ -946,4 +954,4 @@ declare class SlackBridge implements Bridge {
|
|
|
946
954
|
private escapeSlack;
|
|
947
955
|
}
|
|
948
956
|
|
|
949
|
-
export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
|
|
957
|
+
export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, type OperatorMessageDeleteCallback, type OperatorMessageEditCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
|
package/dist/index.js
CHANGED
|
@@ -1181,6 +1181,35 @@ var WebhookHandler = class {
|
|
|
1181
1181
|
try {
|
|
1182
1182
|
const body = await this.parseBody(req);
|
|
1183
1183
|
const update = body;
|
|
1184
|
+
if (update.edited_message) {
|
|
1185
|
+
const msg = update.edited_message;
|
|
1186
|
+
if (msg.text?.startsWith("/")) {
|
|
1187
|
+
this.writeOK(res);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const text = msg.text ?? msg.caption ?? "";
|
|
1191
|
+
if (!text) {
|
|
1192
|
+
this.writeOK(res);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const topicId = msg.message_thread_id;
|
|
1196
|
+
if (!topicId) {
|
|
1197
|
+
this.writeOK(res);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (this.config.onOperatorMessageEdit) {
|
|
1201
|
+
const editedAt = msg.edit_date ? new Date(msg.edit_date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1202
|
+
await this.config.onOperatorMessageEdit(
|
|
1203
|
+
String(topicId),
|
|
1204
|
+
msg.message_id,
|
|
1205
|
+
text,
|
|
1206
|
+
"telegram",
|
|
1207
|
+
editedAt
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
this.writeOK(res);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1184
1213
|
if (update.message) {
|
|
1185
1214
|
const msg = update.message;
|
|
1186
1215
|
if (msg.text?.startsWith("/")) {
|
|
@@ -1236,6 +1265,7 @@ var WebhookHandler = class {
|
|
|
1236
1265
|
return;
|
|
1237
1266
|
}
|
|
1238
1267
|
const operatorName = msg.from?.first_name ?? "Operator";
|
|
1268
|
+
const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
|
|
1239
1269
|
const attachments = [];
|
|
1240
1270
|
if (media) {
|
|
1241
1271
|
const data = await this.downloadTelegramFile(media.fileId);
|
|
@@ -1254,7 +1284,9 @@ var WebhookHandler = class {
|
|
|
1254
1284
|
text,
|
|
1255
1285
|
operatorName,
|
|
1256
1286
|
"telegram",
|
|
1257
|
-
attachments
|
|
1287
|
+
attachments,
|
|
1288
|
+
replyToBridgeMessageId,
|
|
1289
|
+
msg.message_id
|
|
1258
1290
|
);
|
|
1259
1291
|
}
|
|
1260
1292
|
this.writeOK(res);
|
|
@@ -1285,7 +1317,56 @@ var WebhookHandler = class {
|
|
|
1285
1317
|
}
|
|
1286
1318
|
if (payload.type === "event_callback" && payload.event) {
|
|
1287
1319
|
const event = payload.event;
|
|
1288
|
-
const
|
|
1320
|
+
const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
|
|
1321
|
+
if (event.subtype === "message_changed") {
|
|
1322
|
+
if (!this.config.onOperatorMessageEdit) {
|
|
1323
|
+
this.writeOK(res);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
|
|
1327
|
+
if (botId && !isAllowedBot(botId)) {
|
|
1328
|
+
this.writeOK(res);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
|
|
1332
|
+
const messageTs = event.message?.ts ?? event.previous_message?.ts;
|
|
1333
|
+
const text = event.message?.text ?? "";
|
|
1334
|
+
if (threadTs && messageTs) {
|
|
1335
|
+
await this.config.onOperatorMessageEdit(
|
|
1336
|
+
threadTs,
|
|
1337
|
+
messageTs,
|
|
1338
|
+
text,
|
|
1339
|
+
"slack",
|
|
1340
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
this.writeOK(res);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (event.subtype === "message_deleted") {
|
|
1347
|
+
if (!this.config.onOperatorMessageDelete) {
|
|
1348
|
+
this.writeOK(res);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const botId = event.previous_message?.bot_id ?? event.bot_id;
|
|
1352
|
+
if (botId && !isAllowedBot(botId)) {
|
|
1353
|
+
this.writeOK(res);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const threadTs = event.previous_message?.thread_ts;
|
|
1357
|
+
const messageTs = event.deleted_ts ?? event.previous_message?.ts;
|
|
1358
|
+
if (threadTs && messageTs) {
|
|
1359
|
+
await this.config.onOperatorMessageDelete(
|
|
1360
|
+
threadTs,
|
|
1361
|
+
messageTs,
|
|
1362
|
+
"slack",
|
|
1363
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
this.writeOK(res);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
|
|
1289
1370
|
const hasFiles = event.files && event.files.length > 0;
|
|
1290
1371
|
if (hasContent && (event.text || hasFiles)) {
|
|
1291
1372
|
const threadTs = event.thread_ts;
|
|
@@ -1315,7 +1396,9 @@ var WebhookHandler = class {
|
|
|
1315
1396
|
text,
|
|
1316
1397
|
operatorName,
|
|
1317
1398
|
"slack",
|
|
1318
|
-
attachments
|
|
1399
|
+
attachments,
|
|
1400
|
+
null,
|
|
1401
|
+
event.ts ?? null
|
|
1319
1402
|
);
|
|
1320
1403
|
}
|
|
1321
1404
|
}
|
|
@@ -1353,7 +1436,8 @@ var WebhookHandler = class {
|
|
|
1353
1436
|
content,
|
|
1354
1437
|
operatorName,
|
|
1355
1438
|
"discord",
|
|
1356
|
-
[]
|
|
1439
|
+
[],
|
|
1440
|
+
null
|
|
1357
1441
|
);
|
|
1358
1442
|
res.setHeader("Content-Type", "application/json");
|
|
1359
1443
|
res.end(
|
|
@@ -1465,7 +1549,8 @@ var TelegramBridge = class {
|
|
|
1465
1549
|
/**
|
|
1466
1550
|
* Initialize the bridge (optional setup)
|
|
1467
1551
|
*/
|
|
1468
|
-
async init(
|
|
1552
|
+
async init(pocketping) {
|
|
1553
|
+
this.pocketping = pocketping;
|
|
1469
1554
|
try {
|
|
1470
1555
|
const response = await fetch(`${this.baseUrl}/getMe`);
|
|
1471
1556
|
const data = await response.json();
|
|
@@ -1494,8 +1579,18 @@ var TelegramBridge = class {
|
|
|
1494
1579
|
*/
|
|
1495
1580
|
async onVisitorMessage(message, session) {
|
|
1496
1581
|
const text = this.formatVisitorMessage(session.visitorId, message.content);
|
|
1582
|
+
let replyToMessageId;
|
|
1583
|
+
if (message.replyTo) {
|
|
1584
|
+
const storage = this.pocketping?.getStorage();
|
|
1585
|
+
if (storage?.getBridgeMessageIds) {
|
|
1586
|
+
const ids = await storage.getBridgeMessageIds(message.replyTo);
|
|
1587
|
+
if (ids?.telegramMessageId) {
|
|
1588
|
+
replyToMessageId = ids.telegramMessageId;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1497
1592
|
try {
|
|
1498
|
-
const messageId = await this.sendMessage(text);
|
|
1593
|
+
const messageId = await this.sendMessage(text, replyToMessageId);
|
|
1499
1594
|
return { messageId };
|
|
1500
1595
|
} catch (error) {
|
|
1501
1596
|
console.error("[TelegramBridge] Failed to send visitor message:", error);
|
|
@@ -1632,7 +1727,7 @@ ${dataStr}
|
|
|
1632
1727
|
/**
|
|
1633
1728
|
* Send a message to the Telegram chat
|
|
1634
1729
|
*/
|
|
1635
|
-
async sendMessage(text) {
|
|
1730
|
+
async sendMessage(text, replyToMessageId) {
|
|
1636
1731
|
const response = await fetch(`${this.baseUrl}/sendMessage`, {
|
|
1637
1732
|
method: "POST",
|
|
1638
1733
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1640,7 +1735,8 @@ ${dataStr}
|
|
|
1640
1735
|
chat_id: this.chatId,
|
|
1641
1736
|
text,
|
|
1642
1737
|
parse_mode: this.parseMode,
|
|
1643
|
-
disable_notification: this.disableNotification
|
|
1738
|
+
disable_notification: this.disableNotification,
|
|
1739
|
+
...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
|
|
1644
1740
|
})
|
|
1645
1741
|
});
|
|
1646
1742
|
const data = await response.json();
|
|
@@ -1737,7 +1833,8 @@ var DiscordBridge = class _DiscordBridge {
|
|
|
1737
1833
|
/**
|
|
1738
1834
|
* Initialize the bridge (optional setup)
|
|
1739
1835
|
*/
|
|
1740
|
-
async init(
|
|
1836
|
+
async init(pocketping) {
|
|
1837
|
+
this.pocketping = pocketping;
|
|
1741
1838
|
if (this.mode === "bot" && this.botToken) {
|
|
1742
1839
|
try {
|
|
1743
1840
|
const response = await fetch("https://discord.com/api/v10/users/@me", {
|
|
@@ -1787,8 +1884,18 @@ var DiscordBridge = class _DiscordBridge {
|
|
|
1787
1884
|
// Green
|
|
1788
1885
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1789
1886
|
};
|
|
1887
|
+
let replyToMessageId;
|
|
1888
|
+
if (message.replyTo) {
|
|
1889
|
+
const storage = this.pocketping?.getStorage();
|
|
1890
|
+
if (storage?.getBridgeMessageIds) {
|
|
1891
|
+
const ids = await storage.getBridgeMessageIds(message.replyTo);
|
|
1892
|
+
if (ids?.discordMessageId) {
|
|
1893
|
+
replyToMessageId = ids.discordMessageId;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1790
1897
|
try {
|
|
1791
|
-
const messageId = await this.sendEmbed(embed);
|
|
1898
|
+
const messageId = await this.sendEmbed(embed, replyToMessageId);
|
|
1792
1899
|
return { messageId };
|
|
1793
1900
|
} catch (error) {
|
|
1794
1901
|
console.error("[DiscordBridge] Failed to send visitor message:", error);
|
|
@@ -1978,7 +2085,7 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
1978
2085
|
/**
|
|
1979
2086
|
* Send an embed to Discord
|
|
1980
2087
|
*/
|
|
1981
|
-
async sendEmbed(embed) {
|
|
2088
|
+
async sendEmbed(embed, replyToMessageId) {
|
|
1982
2089
|
const body = {
|
|
1983
2090
|
embeds: [embed]
|
|
1984
2091
|
};
|
|
@@ -1989,6 +2096,9 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
1989
2096
|
body.avatar_url = this.avatarUrl;
|
|
1990
2097
|
}
|
|
1991
2098
|
if (this.mode === "webhook" && this.webhookUrl) {
|
|
2099
|
+
if (replyToMessageId) {
|
|
2100
|
+
body.message_reference = { message_id: replyToMessageId };
|
|
2101
|
+
}
|
|
1992
2102
|
const response = await fetch(`${this.webhookUrl}?wait=true`, {
|
|
1993
2103
|
method: "POST",
|
|
1994
2104
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2001,6 +2111,9 @@ ${JSON.stringify(event.data, null, 2)}
|
|
|
2001
2111
|
const data = await response.json();
|
|
2002
2112
|
return data.id;
|
|
2003
2113
|
} else if (this.mode === "bot" && this.channelId) {
|
|
2114
|
+
if (replyToMessageId) {
|
|
2115
|
+
body.message_reference = { message_id: replyToMessageId };
|
|
2116
|
+
}
|
|
2004
2117
|
const response = await fetch(
|
|
2005
2118
|
`https://discord.com/api/v10/channels/${this.channelId}/messages`,
|
|
2006
2119
|
{
|
|
@@ -2063,7 +2176,8 @@ var SlackBridge = class _SlackBridge {
|
|
|
2063
2176
|
/**
|
|
2064
2177
|
* Initialize the bridge (optional setup)
|
|
2065
2178
|
*/
|
|
2066
|
-
async init(
|
|
2179
|
+
async init(pocketping) {
|
|
2180
|
+
this.pocketping = pocketping;
|
|
2067
2181
|
if (this.mode === "bot" && this.botToken) {
|
|
2068
2182
|
try {
|
|
2069
2183
|
const response = await fetch("https://slack.com/api/auth.test", {
|
|
@@ -2123,7 +2237,24 @@ ${url}`
|
|
|
2123
2237
|
* Returns the Slack message timestamp for edit/delete sync.
|
|
2124
2238
|
*/
|
|
2125
2239
|
async onVisitorMessage(message, session) {
|
|
2126
|
-
const blocks = [
|
|
2240
|
+
const blocks = [];
|
|
2241
|
+
if (message.replyTo && this.pocketping?.getStorage().getMessage) {
|
|
2242
|
+
const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
|
|
2243
|
+
if (replyTarget) {
|
|
2244
|
+
const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
|
|
2245
|
+
const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
|
|
2246
|
+
const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
|
|
2247
|
+
const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
|
|
2248
|
+
blocks.push({
|
|
2249
|
+
type: "section",
|
|
2250
|
+
text: {
|
|
2251
|
+
type: "mrkdwn",
|
|
2252
|
+
text: quoted
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
blocks.push(
|
|
2127
2258
|
{
|
|
2128
2259
|
type: "section",
|
|
2129
2260
|
text: {
|
|
@@ -2141,7 +2272,7 @@ ${this.escapeSlack(message.content)}`
|
|
|
2141
2272
|
}
|
|
2142
2273
|
]
|
|
2143
2274
|
}
|
|
2144
|
-
|
|
2275
|
+
);
|
|
2145
2276
|
try {
|
|
2146
2277
|
const messageId = await this.sendBlocks(blocks);
|
|
2147
2278
|
return { messageId };
|