@chat-adapter/gchat 4.2.0 → 4.3.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 +174 -22
- package/dist/index.d.ts +16 -31
- package/dist/index.js +294 -278
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
AdapterRateLimitError,
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
extractCard,
|
|
6
|
+
extractFiles,
|
|
7
|
+
NetworkError,
|
|
8
|
+
ValidationError as ValidationError2
|
|
9
|
+
} from "@chat-adapter/shared";
|
|
10
|
+
import { convertEmojiPlaceholders, defaultEmojiResolver } from "chat";
|
|
8
11
|
import { google as google2 } from "googleapis";
|
|
9
12
|
|
|
10
13
|
// src/cards.ts
|
|
11
14
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
15
|
+
createEmojiConverter,
|
|
16
|
+
cardToFallbackText as sharedCardToFallbackText
|
|
17
|
+
} from "@chat-adapter/shared";
|
|
18
|
+
var convertEmoji = createEmojiConverter("gchat");
|
|
17
19
|
function cardToGoogleCard(card, options) {
|
|
18
20
|
const opts = typeof options === "string" ? { cardId: options } : options || {};
|
|
19
21
|
const sections = [];
|
|
@@ -154,39 +156,29 @@ function convertFieldsToWidgets(element) {
|
|
|
154
156
|
}));
|
|
155
157
|
}
|
|
156
158
|
function cardToFallbackText(card) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
parts.push(convertEmoji(card.subtitle));
|
|
163
|
-
}
|
|
164
|
-
for (const child of card.children) {
|
|
165
|
-
const text = childToFallbackText(child);
|
|
166
|
-
if (text) {
|
|
167
|
-
parts.push(text);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return parts.join("\n");
|
|
171
|
-
}
|
|
172
|
-
function childToFallbackText(child) {
|
|
173
|
-
switch (child.type) {
|
|
174
|
-
case "text":
|
|
175
|
-
return convertEmoji(child.content);
|
|
176
|
-
case "fields":
|
|
177
|
-
return child.children.map((f) => `*${convertEmoji(f.label)}*: ${convertEmoji(f.value)}`).join("\n");
|
|
178
|
-
case "actions":
|
|
179
|
-
return `[${child.children.map((b) => convertEmoji(b.label)).join("] [")}]`;
|
|
180
|
-
case "section":
|
|
181
|
-
return child.children.map((c) => childToFallbackText(c)).filter(Boolean).join("\n");
|
|
182
|
-
default:
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
159
|
+
return sharedCardToFallbackText(card, {
|
|
160
|
+
boldFormat: "*",
|
|
161
|
+
lineBreak: "\n",
|
|
162
|
+
platform: "gchat"
|
|
163
|
+
});
|
|
185
164
|
}
|
|
186
165
|
|
|
187
166
|
// src/markdown.ts
|
|
188
167
|
import {
|
|
189
168
|
BaseFormatConverter,
|
|
169
|
+
getNodeChildren,
|
|
170
|
+
getNodeValue,
|
|
171
|
+
isBlockquoteNode,
|
|
172
|
+
isCodeNode,
|
|
173
|
+
isDeleteNode,
|
|
174
|
+
isEmphasisNode,
|
|
175
|
+
isInlineCodeNode,
|
|
176
|
+
isLinkNode,
|
|
177
|
+
isListItemNode,
|
|
178
|
+
isListNode,
|
|
179
|
+
isParagraphNode,
|
|
180
|
+
isStrongNode,
|
|
181
|
+
isTextNode,
|
|
190
182
|
parseMarkdown
|
|
191
183
|
} from "chat";
|
|
192
184
|
var GoogleChatFormatConverter = class extends BaseFormatConverter {
|
|
@@ -194,11 +186,7 @@ var GoogleChatFormatConverter = class extends BaseFormatConverter {
|
|
|
194
186
|
* Render an AST to Google Chat format.
|
|
195
187
|
*/
|
|
196
188
|
fromAst(ast) {
|
|
197
|
-
|
|
198
|
-
for (const node of ast.children) {
|
|
199
|
-
parts.push(this.nodeToGChat(node));
|
|
200
|
-
}
|
|
201
|
-
return parts.join("\n\n");
|
|
189
|
+
return this.fromAstWithNodeConverter(ast, (node) => this.nodeToGChat(node));
|
|
202
190
|
}
|
|
203
191
|
/**
|
|
204
192
|
* Parse Google Chat message into an AST.
|
|
@@ -210,57 +198,155 @@ var GoogleChatFormatConverter = class extends BaseFormatConverter {
|
|
|
210
198
|
return parseMarkdown(markdown);
|
|
211
199
|
}
|
|
212
200
|
nodeToGChat(node) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
201
|
+
if (isParagraphNode(node)) {
|
|
202
|
+
return getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
203
|
+
}
|
|
204
|
+
if (isTextNode(node)) {
|
|
205
|
+
return node.value;
|
|
206
|
+
}
|
|
207
|
+
if (isStrongNode(node)) {
|
|
208
|
+
const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
209
|
+
return `*${content}*`;
|
|
210
|
+
}
|
|
211
|
+
if (isEmphasisNode(node)) {
|
|
212
|
+
const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
213
|
+
return `_${content}_`;
|
|
214
|
+
}
|
|
215
|
+
if (isDeleteNode(node)) {
|
|
216
|
+
const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
217
|
+
return `~${content}~`;
|
|
218
|
+
}
|
|
219
|
+
if (isInlineCodeNode(node)) {
|
|
220
|
+
return `\`${node.value}\``;
|
|
221
|
+
}
|
|
222
|
+
if (isCodeNode(node)) {
|
|
223
|
+
return `\`\`\`
|
|
224
|
+
${node.value}
|
|
231
225
|
\`\`\``;
|
|
226
|
+
}
|
|
227
|
+
if (isLinkNode(node)) {
|
|
228
|
+
const linkText = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
229
|
+
if (linkText === node.url) {
|
|
230
|
+
return node.url;
|
|
232
231
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
232
|
+
return `${linkText} (${node.url})`;
|
|
233
|
+
}
|
|
234
|
+
if (isBlockquoteNode(node)) {
|
|
235
|
+
return getNodeChildren(node).map((child) => `> ${this.nodeToGChat(child)}`).join("\n");
|
|
236
|
+
}
|
|
237
|
+
if (isListNode(node)) {
|
|
238
|
+
return getNodeChildren(node).map((item, i) => {
|
|
239
|
+
const prefix = node.ordered ? `${i + 1}.` : "\u2022";
|
|
240
|
+
const content = getNodeChildren(item).map((child) => this.nodeToGChat(child)).join("");
|
|
241
|
+
return `${prefix} ${content}`;
|
|
242
|
+
}).join("\n");
|
|
243
|
+
}
|
|
244
|
+
if (isListItemNode(node)) {
|
|
245
|
+
return getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
|
|
246
|
+
}
|
|
247
|
+
if (node.type === "break") {
|
|
248
|
+
return "\n";
|
|
249
|
+
}
|
|
250
|
+
if (node.type === "thematicBreak") {
|
|
251
|
+
return "---";
|
|
252
|
+
}
|
|
253
|
+
const children = getNodeChildren(node);
|
|
254
|
+
if (children.length > 0) {
|
|
255
|
+
return children.map((child) => this.nodeToGChat(child)).join("");
|
|
256
|
+
}
|
|
257
|
+
return getNodeValue(node);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/thread-utils.ts
|
|
262
|
+
import { ValidationError } from "@chat-adapter/shared";
|
|
263
|
+
function encodeThreadId(platformData) {
|
|
264
|
+
const threadPart = platformData.threadName ? `:${Buffer.from(platformData.threadName).toString("base64url")}` : "";
|
|
265
|
+
const dmPart = platformData.isDM ? ":dm" : "";
|
|
266
|
+
return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;
|
|
267
|
+
}
|
|
268
|
+
function decodeThreadId(threadId) {
|
|
269
|
+
const isDM = threadId.endsWith(":dm");
|
|
270
|
+
const cleanId = isDM ? threadId.slice(0, -3) : threadId;
|
|
271
|
+
const parts = cleanId.split(":");
|
|
272
|
+
if (parts.length < 2 || parts[0] !== "gchat") {
|
|
273
|
+
throw new ValidationError(
|
|
274
|
+
"gchat",
|
|
275
|
+
`Invalid Google Chat thread ID: ${threadId}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const spaceName = parts[1];
|
|
279
|
+
const threadName = parts[2] ? Buffer.from(parts[2], "base64url").toString("utf-8") : void 0;
|
|
280
|
+
return { spaceName, threadName, isDM };
|
|
281
|
+
}
|
|
282
|
+
function isDMThread(threadId) {
|
|
283
|
+
return threadId.endsWith(":dm");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/user-info.ts
|
|
287
|
+
var USER_INFO_KEY_PREFIX = "gchat:user:";
|
|
288
|
+
var USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
289
|
+
var UserInfoCache = class {
|
|
290
|
+
constructor(state, logger) {
|
|
291
|
+
this.state = state;
|
|
292
|
+
this.logger = logger;
|
|
293
|
+
}
|
|
294
|
+
inMemoryCache = /* @__PURE__ */ new Map();
|
|
295
|
+
/**
|
|
296
|
+
* Cache user info for later lookup.
|
|
297
|
+
*/
|
|
298
|
+
async set(userId, displayName, email) {
|
|
299
|
+
if (!displayName || displayName === "unknown") return;
|
|
300
|
+
const userInfo = { displayName, email };
|
|
301
|
+
this.inMemoryCache.set(userId, userInfo);
|
|
302
|
+
if (this.state) {
|
|
303
|
+
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
304
|
+
await this.state.set(
|
|
305
|
+
cacheKey,
|
|
306
|
+
userInfo,
|
|
307
|
+
USER_INFO_CACHE_TTL_MS
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get cached user info. Checks in-memory cache first, then falls back to state adapter.
|
|
313
|
+
*/
|
|
314
|
+
async get(userId) {
|
|
315
|
+
const inMemory = this.inMemoryCache.get(userId);
|
|
316
|
+
if (inMemory) {
|
|
317
|
+
return inMemory;
|
|
318
|
+
}
|
|
319
|
+
if (!this.state) return null;
|
|
320
|
+
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
321
|
+
const fromState = await this.state.get(cacheKey);
|
|
322
|
+
if (fromState) {
|
|
323
|
+
this.inMemoryCache.set(userId, fromState);
|
|
324
|
+
}
|
|
325
|
+
return fromState;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Resolve user display name, using cache if available.
|
|
329
|
+
*
|
|
330
|
+
* @param userId - The user's resource name (e.g., "users/123456")
|
|
331
|
+
* @param providedDisplayName - Display name from the message if available
|
|
332
|
+
* @param botUserId - The bot's user ID (for self-identification)
|
|
333
|
+
* @param botUserName - The bot's configured username
|
|
334
|
+
*/
|
|
335
|
+
async resolveDisplayName(userId, providedDisplayName, botUserId, botUserName) {
|
|
336
|
+
if (providedDisplayName && providedDisplayName !== "unknown") {
|
|
337
|
+
this.set(userId, providedDisplayName).catch((err) => {
|
|
338
|
+
this.logger.error("Failed to cache user info", { userId, error: err });
|
|
339
|
+
});
|
|
340
|
+
return providedDisplayName;
|
|
341
|
+
}
|
|
342
|
+
if (botUserId && userId === botUserId) {
|
|
343
|
+
return botUserName;
|
|
263
344
|
}
|
|
345
|
+
const cached = await this.get(userId);
|
|
346
|
+
if (cached?.displayName) {
|
|
347
|
+
return cached.displayName;
|
|
348
|
+
}
|
|
349
|
+
return userId.replace("users/", "User ");
|
|
264
350
|
}
|
|
265
351
|
};
|
|
266
352
|
|
|
@@ -409,8 +495,6 @@ function verifyPubSubRequest(request, _expectedAudience) {
|
|
|
409
495
|
var SUBSCRIPTION_REFRESH_BUFFER_MS = 60 * 60 * 1e3;
|
|
410
496
|
var SUBSCRIPTION_CACHE_TTL_MS = 25 * 60 * 60 * 1e3;
|
|
411
497
|
var SPACE_SUB_KEY_PREFIX = "gchat:space-sub:";
|
|
412
|
-
var USER_INFO_KEY_PREFIX = "gchat:user:";
|
|
413
|
-
var USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
414
498
|
var GoogleChatAdapter = class {
|
|
415
499
|
name = "gchat";
|
|
416
500
|
userName;
|
|
@@ -419,7 +503,7 @@ var GoogleChatAdapter = class {
|
|
|
419
503
|
chatApi;
|
|
420
504
|
chat = null;
|
|
421
505
|
state = null;
|
|
422
|
-
logger
|
|
506
|
+
logger;
|
|
423
507
|
formatConverter = new GoogleChatFormatConverter();
|
|
424
508
|
pubsubTopic;
|
|
425
509
|
credentials;
|
|
@@ -436,10 +520,12 @@ var GoogleChatAdapter = class {
|
|
|
436
520
|
impersonatedChatApi;
|
|
437
521
|
/** HTTP endpoint URL for button click actions */
|
|
438
522
|
endpointUrl;
|
|
439
|
-
/**
|
|
440
|
-
userInfoCache
|
|
523
|
+
/** User info cache for display name lookups - initialized later in initialize() */
|
|
524
|
+
userInfoCache;
|
|
441
525
|
constructor(config) {
|
|
526
|
+
this.logger = config.logger;
|
|
442
527
|
this.userName = config.userName || "bot";
|
|
528
|
+
this.userInfoCache = new UserInfoCache(null, this.logger);
|
|
443
529
|
this.pubsubTopic = config.pubsubTopic;
|
|
444
530
|
this.impersonateUser = config.impersonateUser;
|
|
445
531
|
this.endpointUrl = config.endpointUrl;
|
|
@@ -467,7 +553,8 @@ var GoogleChatAdapter = class {
|
|
|
467
553
|
this.customAuth = config.auth;
|
|
468
554
|
auth = config.auth;
|
|
469
555
|
} else {
|
|
470
|
-
throw new
|
|
556
|
+
throw new ValidationError2(
|
|
557
|
+
"gchat",
|
|
471
558
|
"GoogleChatAdapter requires one of: credentials, useApplicationDefaultCredentials, or auth"
|
|
472
559
|
);
|
|
473
560
|
}
|
|
@@ -510,12 +597,12 @@ var GoogleChatAdapter = class {
|
|
|
510
597
|
async initialize(chat) {
|
|
511
598
|
this.chat = chat;
|
|
512
599
|
this.state = chat.getState();
|
|
513
|
-
this.
|
|
600
|
+
this.userInfoCache = new UserInfoCache(this.state, this.logger);
|
|
514
601
|
if (!this.botUserId) {
|
|
515
602
|
const savedBotUserId = await this.state.get("gchat:botUserId");
|
|
516
603
|
if (savedBotUserId) {
|
|
517
604
|
this.botUserId = savedBotUserId;
|
|
518
|
-
this.logger
|
|
605
|
+
this.logger.debug("Restored bot user ID from state", {
|
|
519
606
|
botUserId: this.botUserId
|
|
520
607
|
});
|
|
521
608
|
}
|
|
@@ -526,13 +613,13 @@ var GoogleChatAdapter = class {
|
|
|
526
613
|
* Ensures the space has a Workspace Events subscription so we receive all messages.
|
|
527
614
|
*/
|
|
528
615
|
async onThreadSubscribe(threadId) {
|
|
529
|
-
this.logger
|
|
616
|
+
this.logger.info("onThreadSubscribe called", {
|
|
530
617
|
threadId,
|
|
531
618
|
hasPubsubTopic: !!this.pubsubTopic,
|
|
532
619
|
pubsubTopic: this.pubsubTopic
|
|
533
620
|
});
|
|
534
621
|
if (!this.pubsubTopic) {
|
|
535
|
-
this.logger
|
|
622
|
+
this.logger.warn(
|
|
536
623
|
"No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var."
|
|
537
624
|
);
|
|
538
625
|
return;
|
|
@@ -545,7 +632,7 @@ var GoogleChatAdapter = class {
|
|
|
545
632
|
* Creates one if it doesn't exist or is about to expire.
|
|
546
633
|
*/
|
|
547
634
|
async ensureSpaceSubscription(spaceName) {
|
|
548
|
-
this.logger
|
|
635
|
+
this.logger.info("ensureSpaceSubscription called", {
|
|
549
636
|
spaceName,
|
|
550
637
|
hasPubsubTopic: !!this.pubsubTopic,
|
|
551
638
|
hasState: !!this.state,
|
|
@@ -553,7 +640,7 @@ var GoogleChatAdapter = class {
|
|
|
553
640
|
hasADC: this.useADC
|
|
554
641
|
});
|
|
555
642
|
if (!this.pubsubTopic || !this.state) {
|
|
556
|
-
this.logger
|
|
643
|
+
this.logger.warn("ensureSpaceSubscription skipped - missing config", {
|
|
557
644
|
hasPubsubTopic: !!this.pubsubTopic,
|
|
558
645
|
hasState: !!this.state
|
|
559
646
|
});
|
|
@@ -564,20 +651,20 @@ var GoogleChatAdapter = class {
|
|
|
564
651
|
if (cached) {
|
|
565
652
|
const timeUntilExpiry = cached.expireTime - Date.now();
|
|
566
653
|
if (timeUntilExpiry > SUBSCRIPTION_REFRESH_BUFFER_MS) {
|
|
567
|
-
this.logger
|
|
654
|
+
this.logger.debug("Space subscription still valid", {
|
|
568
655
|
spaceName,
|
|
569
656
|
expiresIn: Math.round(timeUntilExpiry / 1e3 / 60)
|
|
570
657
|
});
|
|
571
658
|
return;
|
|
572
659
|
}
|
|
573
|
-
this.logger
|
|
660
|
+
this.logger.debug("Space subscription expiring soon, will refresh", {
|
|
574
661
|
spaceName,
|
|
575
662
|
expiresIn: Math.round(timeUntilExpiry / 1e3 / 60)
|
|
576
663
|
});
|
|
577
664
|
}
|
|
578
665
|
const pending = this.pendingSubscriptions.get(spaceName);
|
|
579
666
|
if (pending) {
|
|
580
|
-
this.logger
|
|
667
|
+
this.logger.debug("Subscription creation already in progress", {
|
|
581
668
|
spaceName
|
|
582
669
|
});
|
|
583
670
|
return pending;
|
|
@@ -598,14 +685,14 @@ var GoogleChatAdapter = class {
|
|
|
598
685
|
*/
|
|
599
686
|
async createSpaceSubscriptionWithCache(spaceName, cacheKey) {
|
|
600
687
|
const authOptions = this.getAuthOptions();
|
|
601
|
-
this.logger
|
|
688
|
+
this.logger.info("createSpaceSubscriptionWithCache", {
|
|
602
689
|
spaceName,
|
|
603
690
|
hasAuthOptions: !!authOptions,
|
|
604
691
|
hasCredentials: !!this.credentials,
|
|
605
692
|
hasADC: this.useADC
|
|
606
693
|
});
|
|
607
694
|
if (!authOptions) {
|
|
608
|
-
this.logger
|
|
695
|
+
this.logger.error(
|
|
609
696
|
"Cannot create subscription: no auth configured. Use GOOGLE_CHAT_CREDENTIALS, GOOGLE_CHAT_USE_ADC=true, or custom auth."
|
|
610
697
|
);
|
|
611
698
|
return;
|
|
@@ -618,7 +705,7 @@ var GoogleChatAdapter = class {
|
|
|
618
705
|
authOptions
|
|
619
706
|
);
|
|
620
707
|
if (existing) {
|
|
621
|
-
this.logger
|
|
708
|
+
this.logger.debug("Found existing subscription", {
|
|
622
709
|
spaceName,
|
|
623
710
|
subscriptionName: existing.subscriptionName
|
|
624
711
|
});
|
|
@@ -631,7 +718,7 @@ var GoogleChatAdapter = class {
|
|
|
631
718
|
}
|
|
632
719
|
return;
|
|
633
720
|
}
|
|
634
|
-
this.logger
|
|
721
|
+
this.logger.info("Creating Workspace Events subscription", {
|
|
635
722
|
spaceName,
|
|
636
723
|
pubsubTopic
|
|
637
724
|
});
|
|
@@ -650,13 +737,13 @@ var GoogleChatAdapter = class {
|
|
|
650
737
|
SUBSCRIPTION_CACHE_TTL_MS
|
|
651
738
|
);
|
|
652
739
|
}
|
|
653
|
-
this.logger
|
|
740
|
+
this.logger.info("Workspace Events subscription created", {
|
|
654
741
|
spaceName,
|
|
655
742
|
subscriptionName: result.name,
|
|
656
743
|
expireTime: result.expireTime
|
|
657
744
|
});
|
|
658
745
|
} catch (error) {
|
|
659
|
-
this.logger
|
|
746
|
+
this.logger.error("Failed to create Workspace Events subscription", {
|
|
660
747
|
spaceName,
|
|
661
748
|
error
|
|
662
749
|
});
|
|
@@ -681,7 +768,7 @@ var GoogleChatAdapter = class {
|
|
|
681
768
|
}
|
|
682
769
|
}
|
|
683
770
|
} catch (error) {
|
|
684
|
-
this.logger
|
|
771
|
+
this.logger.error("Error checking existing subscriptions", { error });
|
|
685
772
|
}
|
|
686
773
|
return null;
|
|
687
774
|
}
|
|
@@ -711,14 +798,14 @@ var GoogleChatAdapter = class {
|
|
|
711
798
|
try {
|
|
712
799
|
const url = new URL(request.url);
|
|
713
800
|
this.endpointUrl = url.toString();
|
|
714
|
-
this.logger
|
|
801
|
+
this.logger.debug("Auto-detected endpoint URL", {
|
|
715
802
|
endpointUrl: this.endpointUrl
|
|
716
803
|
});
|
|
717
804
|
} catch {
|
|
718
805
|
}
|
|
719
806
|
}
|
|
720
807
|
const body = await request.text();
|
|
721
|
-
this.logger
|
|
808
|
+
this.logger.debug("GChat webhook raw body", { body });
|
|
722
809
|
let parsed;
|
|
723
810
|
try {
|
|
724
811
|
parsed = JSON.parse(body);
|
|
@@ -732,7 +819,7 @@ var GoogleChatAdapter = class {
|
|
|
732
819
|
const event = parsed;
|
|
733
820
|
const addedPayload = event.chat?.addedToSpacePayload;
|
|
734
821
|
if (addedPayload) {
|
|
735
|
-
this.logger
|
|
822
|
+
this.logger.debug("Bot added to space", {
|
|
736
823
|
space: addedPayload.space.name,
|
|
737
824
|
spaceType: addedPayload.space.type
|
|
738
825
|
});
|
|
@@ -740,7 +827,7 @@ var GoogleChatAdapter = class {
|
|
|
740
827
|
}
|
|
741
828
|
const removedPayload = event.chat?.removedFromSpacePayload;
|
|
742
829
|
if (removedPayload) {
|
|
743
|
-
this.logger
|
|
830
|
+
this.logger.debug("Bot removed from space", {
|
|
744
831
|
space: removedPayload.space.name
|
|
745
832
|
});
|
|
746
833
|
}
|
|
@@ -754,14 +841,14 @@ var GoogleChatAdapter = class {
|
|
|
754
841
|
}
|
|
755
842
|
const messagePayload = event.chat?.messagePayload;
|
|
756
843
|
if (messagePayload) {
|
|
757
|
-
this.logger
|
|
844
|
+
this.logger.debug("message event", {
|
|
758
845
|
space: messagePayload.space.name,
|
|
759
846
|
sender: messagePayload.message.sender?.displayName,
|
|
760
847
|
text: messagePayload.message.text?.slice(0, 50)
|
|
761
848
|
});
|
|
762
849
|
this.handleMessageEvent(event, options);
|
|
763
850
|
} else if (!addedPayload && !removedPayload) {
|
|
764
|
-
this.logger
|
|
851
|
+
this.logger.debug("Non-message event received", {
|
|
765
852
|
hasChat: !!event.chat,
|
|
766
853
|
hasCommonEventObject: !!event.commonEventObject
|
|
767
854
|
});
|
|
@@ -782,14 +869,14 @@ var GoogleChatAdapter = class {
|
|
|
782
869
|
"google.workspace.chat.reaction.v1.deleted"
|
|
783
870
|
];
|
|
784
871
|
if (eventType && !allowedEventTypes.includes(eventType)) {
|
|
785
|
-
this.logger
|
|
872
|
+
this.logger.debug("Skipping unsupported Pub/Sub event", { eventType });
|
|
786
873
|
return new Response(JSON.stringify({ success: true }), {
|
|
787
874
|
headers: { "Content-Type": "application/json" }
|
|
788
875
|
});
|
|
789
876
|
}
|
|
790
877
|
try {
|
|
791
878
|
const notification = decodePubSubMessage(pushMessage);
|
|
792
|
-
this.logger
|
|
879
|
+
this.logger.debug("Pub/Sub notification decoded", {
|
|
793
880
|
eventType: notification.eventType,
|
|
794
881
|
messageId: notification.message?.name,
|
|
795
882
|
reactionName: notification.reaction?.name
|
|
@@ -804,7 +891,7 @@ var GoogleChatAdapter = class {
|
|
|
804
891
|
headers: { "Content-Type": "application/json" }
|
|
805
892
|
});
|
|
806
893
|
} catch (error) {
|
|
807
|
-
this.logger
|
|
894
|
+
this.logger.error("Error processing Pub/Sub message", { error });
|
|
808
895
|
return new Response(JSON.stringify({ error: "Processing failed" }), {
|
|
809
896
|
status: 200,
|
|
810
897
|
headers: { "Content-Type": "application/json" }
|
|
@@ -832,7 +919,10 @@ var GoogleChatAdapter = class {
|
|
|
832
919
|
if (resolvedSpaceName && options?.waitUntil) {
|
|
833
920
|
options.waitUntil(
|
|
834
921
|
this.ensureSpaceSubscription(resolvedSpaceName).catch((err) => {
|
|
835
|
-
this.logger
|
|
922
|
+
this.logger.error("Subscription refresh failed", {
|
|
923
|
+
spaceName: resolvedSpaceName,
|
|
924
|
+
error: err
|
|
925
|
+
});
|
|
836
926
|
})
|
|
837
927
|
);
|
|
838
928
|
}
|
|
@@ -878,13 +968,13 @@ var GoogleChatAdapter = class {
|
|
|
878
968
|
spaceName: spaceName || "",
|
|
879
969
|
threadName: threadName ?? void 0
|
|
880
970
|
});
|
|
881
|
-
this.logger
|
|
971
|
+
this.logger.debug("Fetched thread context for reaction", {
|
|
882
972
|
messageName,
|
|
883
973
|
threadName,
|
|
884
974
|
threadId
|
|
885
975
|
});
|
|
886
976
|
} catch (error) {
|
|
887
|
-
this.logger
|
|
977
|
+
this.logger.warn("Failed to fetch message for thread context", {
|
|
888
978
|
messageName,
|
|
889
979
|
error
|
|
890
980
|
});
|
|
@@ -928,15 +1018,17 @@ var GoogleChatAdapter = class {
|
|
|
928
1018
|
async parsePubSubMessage(notification, threadId) {
|
|
929
1019
|
const message = notification.message;
|
|
930
1020
|
if (!message) {
|
|
931
|
-
throw new
|
|
1021
|
+
throw new ValidationError2("gchat", "PubSub notification missing message");
|
|
932
1022
|
}
|
|
933
1023
|
const text = this.normalizeBotMentions(message);
|
|
934
1024
|
const isBot = message.sender?.type === "BOT";
|
|
935
1025
|
const isMe = this.isMessageFromSelf(message);
|
|
936
1026
|
const userId = message.sender?.name || "unknown";
|
|
937
|
-
const displayName = await this.
|
|
1027
|
+
const displayName = await this.userInfoCache.resolveDisplayName(
|
|
938
1028
|
userId,
|
|
939
|
-
message.sender?.displayName
|
|
1029
|
+
message.sender?.displayName,
|
|
1030
|
+
this.botUserId,
|
|
1031
|
+
this.userName
|
|
940
1032
|
);
|
|
941
1033
|
const parsedMessage = {
|
|
942
1034
|
id: message.name,
|
|
@@ -959,7 +1051,7 @@ var GoogleChatAdapter = class {
|
|
|
959
1051
|
(att) => this.createAttachment(att)
|
|
960
1052
|
)
|
|
961
1053
|
};
|
|
962
|
-
this.logger
|
|
1054
|
+
this.logger.debug("Pub/Sub parsed message", {
|
|
963
1055
|
threadId,
|
|
964
1056
|
messageId: parsedMessage.id,
|
|
965
1057
|
text: parsedMessage.text,
|
|
@@ -985,14 +1077,14 @@ var GoogleChatAdapter = class {
|
|
|
985
1077
|
*/
|
|
986
1078
|
handleCardClick(event, options) {
|
|
987
1079
|
if (!this.chat) {
|
|
988
|
-
this.logger
|
|
1080
|
+
this.logger.warn("Chat instance not initialized, ignoring card click");
|
|
989
1081
|
return;
|
|
990
1082
|
}
|
|
991
1083
|
const buttonPayload = event.chat?.buttonClickedPayload;
|
|
992
1084
|
const commonEvent = event.commonEventObject;
|
|
993
1085
|
const actionId = commonEvent?.parameters?.actionId || commonEvent?.invokedFunction;
|
|
994
1086
|
if (!actionId) {
|
|
995
|
-
this.logger
|
|
1087
|
+
this.logger.debug("Card click missing actionId", {
|
|
996
1088
|
parameters: commonEvent?.parameters,
|
|
997
1089
|
invokedFunction: commonEvent?.invokedFunction
|
|
998
1090
|
});
|
|
@@ -1003,7 +1095,7 @@ var GoogleChatAdapter = class {
|
|
|
1003
1095
|
const message = buttonPayload?.message;
|
|
1004
1096
|
const user = buttonPayload?.user || event.chat?.user;
|
|
1005
1097
|
if (!space) {
|
|
1006
|
-
this.logger
|
|
1098
|
+
this.logger.warn("Card click missing space info");
|
|
1007
1099
|
return;
|
|
1008
1100
|
}
|
|
1009
1101
|
const threadName = message?.thread?.name || message?.name;
|
|
@@ -1026,7 +1118,7 @@ var GoogleChatAdapter = class {
|
|
|
1026
1118
|
adapter: this,
|
|
1027
1119
|
raw: event
|
|
1028
1120
|
};
|
|
1029
|
-
this.logger
|
|
1121
|
+
this.logger.debug("Processing GChat card click", {
|
|
1030
1122
|
actionId,
|
|
1031
1123
|
value,
|
|
1032
1124
|
messageId: actionEvent.messageId,
|
|
@@ -1039,12 +1131,12 @@ var GoogleChatAdapter = class {
|
|
|
1039
1131
|
*/
|
|
1040
1132
|
handleMessageEvent(event, options) {
|
|
1041
1133
|
if (!this.chat) {
|
|
1042
|
-
this.logger
|
|
1134
|
+
this.logger.warn("Chat instance not initialized, ignoring event");
|
|
1043
1135
|
return;
|
|
1044
1136
|
}
|
|
1045
1137
|
const messagePayload = event.chat?.messagePayload;
|
|
1046
1138
|
if (!messagePayload) {
|
|
1047
|
-
this.logger
|
|
1139
|
+
this.logger.debug("Ignoring event without messagePayload");
|
|
1048
1140
|
return;
|
|
1049
1141
|
}
|
|
1050
1142
|
const message = messagePayload.message;
|
|
@@ -1065,7 +1157,7 @@ var GoogleChatAdapter = class {
|
|
|
1065
1157
|
parseGoogleChatMessage(event, threadId) {
|
|
1066
1158
|
const message = event.chat?.messagePayload?.message;
|
|
1067
1159
|
if (!message) {
|
|
1068
|
-
throw new
|
|
1160
|
+
throw new ValidationError2("gchat", "Event has no message payload");
|
|
1069
1161
|
}
|
|
1070
1162
|
const text = this.normalizeBotMentions(message);
|
|
1071
1163
|
const isBot = message.sender?.type === "BOT";
|
|
@@ -1073,10 +1165,9 @@ var GoogleChatAdapter = class {
|
|
|
1073
1165
|
const userId = message.sender?.name || "unknown";
|
|
1074
1166
|
const displayName = message.sender?.displayName || "unknown";
|
|
1075
1167
|
if (userId !== "unknown" && displayName !== "unknown") {
|
|
1076
|
-
this.
|
|
1077
|
-
(
|
|
1078
|
-
|
|
1079
|
-
);
|
|
1168
|
+
this.userInfoCache.set(userId, displayName, message.sender?.email).catch((error) => {
|
|
1169
|
+
this.logger.error("Failed to cache user info", { userId, error });
|
|
1170
|
+
});
|
|
1080
1171
|
}
|
|
1081
1172
|
return {
|
|
1082
1173
|
id: message.name,
|
|
@@ -1103,21 +1194,21 @@ var GoogleChatAdapter = class {
|
|
|
1103
1194
|
async postMessage(threadId, message) {
|
|
1104
1195
|
const { spaceName, threadName } = this.decodeThreadId(threadId);
|
|
1105
1196
|
try {
|
|
1106
|
-
const files =
|
|
1197
|
+
const files = extractFiles(message);
|
|
1107
1198
|
if (files.length > 0) {
|
|
1108
|
-
this.logger
|
|
1199
|
+
this.logger.warn(
|
|
1109
1200
|
"File uploads are not yet supported for Google Chat. Files will be ignored.",
|
|
1110
1201
|
{ fileCount: files.length }
|
|
1111
1202
|
);
|
|
1112
1203
|
}
|
|
1113
|
-
const card =
|
|
1204
|
+
const card = extractCard(message);
|
|
1114
1205
|
if (card) {
|
|
1115
1206
|
const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1116
1207
|
const googleCard = cardToGoogleCard(card, {
|
|
1117
1208
|
cardId,
|
|
1118
1209
|
endpointUrl: this.endpointUrl
|
|
1119
1210
|
});
|
|
1120
|
-
this.logger
|
|
1211
|
+
this.logger.debug("GChat API: spaces.messages.create (card)", {
|
|
1121
1212
|
spaceName,
|
|
1122
1213
|
threadName,
|
|
1123
1214
|
googleCard: JSON.stringify(googleCard)
|
|
@@ -1131,7 +1222,7 @@ var GoogleChatAdapter = class {
|
|
|
1131
1222
|
thread: threadName ? { name: threadName } : void 0
|
|
1132
1223
|
}
|
|
1133
1224
|
});
|
|
1134
|
-
this.logger
|
|
1225
|
+
this.logger.debug("GChat API: spaces.messages.create response", {
|
|
1135
1226
|
messageName: response2.data.name
|
|
1136
1227
|
});
|
|
1137
1228
|
return {
|
|
@@ -1140,11 +1231,11 @@ var GoogleChatAdapter = class {
|
|
|
1140
1231
|
raw: response2.data
|
|
1141
1232
|
};
|
|
1142
1233
|
}
|
|
1143
|
-
const text =
|
|
1234
|
+
const text = convertEmojiPlaceholders(
|
|
1144
1235
|
this.formatConverter.renderPostable(message),
|
|
1145
1236
|
"gchat"
|
|
1146
1237
|
);
|
|
1147
|
-
this.logger
|
|
1238
|
+
this.logger.debug("GChat API: spaces.messages.create", {
|
|
1148
1239
|
spaceName,
|
|
1149
1240
|
threadName,
|
|
1150
1241
|
textLength: text.length
|
|
@@ -1158,7 +1249,7 @@ var GoogleChatAdapter = class {
|
|
|
1158
1249
|
thread: threadName ? { name: threadName } : void 0
|
|
1159
1250
|
}
|
|
1160
1251
|
});
|
|
1161
|
-
this.logger
|
|
1252
|
+
this.logger.debug("GChat API: spaces.messages.create response", {
|
|
1162
1253
|
messageName: response.data.name
|
|
1163
1254
|
});
|
|
1164
1255
|
return {
|
|
@@ -1170,27 +1261,6 @@ var GoogleChatAdapter = class {
|
|
|
1170
1261
|
this.handleGoogleChatError(error, "postMessage");
|
|
1171
1262
|
}
|
|
1172
1263
|
}
|
|
1173
|
-
/**
|
|
1174
|
-
* Extract card element from a message if present.
|
|
1175
|
-
*/
|
|
1176
|
-
extractCard(message) {
|
|
1177
|
-
if (isCardElement(message)) {
|
|
1178
|
-
return message;
|
|
1179
|
-
}
|
|
1180
|
-
if (typeof message === "object" && message !== null && "card" in message) {
|
|
1181
|
-
return message.card;
|
|
1182
|
-
}
|
|
1183
|
-
return null;
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Extract files from a message if present.
|
|
1187
|
-
*/
|
|
1188
|
-
extractFiles(message) {
|
|
1189
|
-
if (typeof message === "object" && message !== null && "files" in message) {
|
|
1190
|
-
return message.files ?? [];
|
|
1191
|
-
}
|
|
1192
|
-
return [];
|
|
1193
|
-
}
|
|
1194
1264
|
/**
|
|
1195
1265
|
* Create an Attachment object from a Google Chat attachment.
|
|
1196
1266
|
*/
|
|
@@ -1213,12 +1283,18 @@ var GoogleChatAdapter = class {
|
|
|
1213
1283
|
mimeType: att.contentType || void 0,
|
|
1214
1284
|
fetchData: url ? async () => {
|
|
1215
1285
|
if (typeof auth === "string" || !auth) {
|
|
1216
|
-
throw new
|
|
1286
|
+
throw new AuthenticationError(
|
|
1287
|
+
"gchat",
|
|
1288
|
+
"Cannot fetch file: no auth client configured"
|
|
1289
|
+
);
|
|
1217
1290
|
}
|
|
1218
1291
|
const tokenResult = await auth.getAccessToken();
|
|
1219
1292
|
const token = typeof tokenResult === "string" ? tokenResult : tokenResult?.token;
|
|
1220
1293
|
if (!token) {
|
|
1221
|
-
throw new
|
|
1294
|
+
throw new AuthenticationError(
|
|
1295
|
+
"gchat",
|
|
1296
|
+
"Failed to get access token"
|
|
1297
|
+
);
|
|
1222
1298
|
}
|
|
1223
1299
|
const response = await fetch(url, {
|
|
1224
1300
|
headers: {
|
|
@@ -1226,7 +1302,8 @@ var GoogleChatAdapter = class {
|
|
|
1226
1302
|
}
|
|
1227
1303
|
});
|
|
1228
1304
|
if (!response.ok) {
|
|
1229
|
-
throw new
|
|
1305
|
+
throw new NetworkError(
|
|
1306
|
+
"gchat",
|
|
1230
1307
|
`Failed to fetch file: ${response.status} ${response.statusText}`
|
|
1231
1308
|
);
|
|
1232
1309
|
}
|
|
@@ -1237,14 +1314,14 @@ var GoogleChatAdapter = class {
|
|
|
1237
1314
|
}
|
|
1238
1315
|
async editMessage(threadId, messageId, message) {
|
|
1239
1316
|
try {
|
|
1240
|
-
const card =
|
|
1317
|
+
const card = extractCard(message);
|
|
1241
1318
|
if (card) {
|
|
1242
1319
|
const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1243
1320
|
const googleCard = cardToGoogleCard(card, {
|
|
1244
1321
|
cardId,
|
|
1245
1322
|
endpointUrl: this.endpointUrl
|
|
1246
1323
|
});
|
|
1247
|
-
this.logger
|
|
1324
|
+
this.logger.debug("GChat API: spaces.messages.update (card)", {
|
|
1248
1325
|
messageId,
|
|
1249
1326
|
cardId
|
|
1250
1327
|
});
|
|
@@ -1256,7 +1333,7 @@ var GoogleChatAdapter = class {
|
|
|
1256
1333
|
cardsV2: [googleCard]
|
|
1257
1334
|
}
|
|
1258
1335
|
});
|
|
1259
|
-
this.logger
|
|
1336
|
+
this.logger.debug("GChat API: spaces.messages.update response", {
|
|
1260
1337
|
messageName: response2.data.name
|
|
1261
1338
|
});
|
|
1262
1339
|
return {
|
|
@@ -1265,11 +1342,11 @@ var GoogleChatAdapter = class {
|
|
|
1265
1342
|
raw: response2.data
|
|
1266
1343
|
};
|
|
1267
1344
|
}
|
|
1268
|
-
const text =
|
|
1345
|
+
const text = convertEmojiPlaceholders(
|
|
1269
1346
|
this.formatConverter.renderPostable(message),
|
|
1270
1347
|
"gchat"
|
|
1271
1348
|
);
|
|
1272
|
-
this.logger
|
|
1349
|
+
this.logger.debug("GChat API: spaces.messages.update", {
|
|
1273
1350
|
messageId,
|
|
1274
1351
|
textLength: text.length
|
|
1275
1352
|
});
|
|
@@ -1280,7 +1357,7 @@ var GoogleChatAdapter = class {
|
|
|
1280
1357
|
text
|
|
1281
1358
|
}
|
|
1282
1359
|
});
|
|
1283
|
-
this.logger
|
|
1360
|
+
this.logger.debug("GChat API: spaces.messages.update response", {
|
|
1284
1361
|
messageName: response.data.name
|
|
1285
1362
|
});
|
|
1286
1363
|
return {
|
|
@@ -1294,11 +1371,11 @@ var GoogleChatAdapter = class {
|
|
|
1294
1371
|
}
|
|
1295
1372
|
async deleteMessage(_threadId, messageId) {
|
|
1296
1373
|
try {
|
|
1297
|
-
this.logger
|
|
1374
|
+
this.logger.debug("GChat API: spaces.messages.delete", { messageId });
|
|
1298
1375
|
await this.chatApi.spaces.messages.delete({
|
|
1299
1376
|
name: messageId
|
|
1300
1377
|
});
|
|
1301
|
-
this.logger
|
|
1378
|
+
this.logger.debug("GChat API: spaces.messages.delete response", {
|
|
1302
1379
|
ok: true
|
|
1303
1380
|
});
|
|
1304
1381
|
} catch (error) {
|
|
@@ -1308,7 +1385,7 @@ var GoogleChatAdapter = class {
|
|
|
1308
1385
|
async addReaction(_threadId, messageId, emoji) {
|
|
1309
1386
|
const gchatEmoji = defaultEmojiResolver.toGChat(emoji);
|
|
1310
1387
|
try {
|
|
1311
|
-
this.logger
|
|
1388
|
+
this.logger.debug("GChat API: spaces.messages.reactions.create", {
|
|
1312
1389
|
messageId,
|
|
1313
1390
|
emoji: gchatEmoji
|
|
1314
1391
|
});
|
|
@@ -1318,7 +1395,7 @@ var GoogleChatAdapter = class {
|
|
|
1318
1395
|
emoji: { unicode: gchatEmoji }
|
|
1319
1396
|
}
|
|
1320
1397
|
});
|
|
1321
|
-
this.logger
|
|
1398
|
+
this.logger.debug(
|
|
1322
1399
|
"GChat API: spaces.messages.reactions.create response",
|
|
1323
1400
|
{
|
|
1324
1401
|
ok: true
|
|
@@ -1331,32 +1408,32 @@ var GoogleChatAdapter = class {
|
|
|
1331
1408
|
async removeReaction(_threadId, messageId, emoji) {
|
|
1332
1409
|
const gchatEmoji = defaultEmojiResolver.toGChat(emoji);
|
|
1333
1410
|
try {
|
|
1334
|
-
this.logger
|
|
1411
|
+
this.logger.debug("GChat API: spaces.messages.reactions.list", {
|
|
1335
1412
|
messageId
|
|
1336
1413
|
});
|
|
1337
1414
|
const response = await this.chatApi.spaces.messages.reactions.list({
|
|
1338
1415
|
parent: messageId
|
|
1339
1416
|
});
|
|
1340
|
-
this.logger
|
|
1417
|
+
this.logger.debug("GChat API: spaces.messages.reactions.list response", {
|
|
1341
1418
|
reactionCount: response.data.reactions?.length || 0
|
|
1342
1419
|
});
|
|
1343
1420
|
const reaction = response.data.reactions?.find(
|
|
1344
1421
|
(r) => r.emoji?.unicode === gchatEmoji
|
|
1345
1422
|
);
|
|
1346
1423
|
if (!reaction?.name) {
|
|
1347
|
-
this.logger
|
|
1424
|
+
this.logger.debug("Reaction not found to remove", {
|
|
1348
1425
|
messageId,
|
|
1349
1426
|
emoji: gchatEmoji
|
|
1350
1427
|
});
|
|
1351
1428
|
return;
|
|
1352
1429
|
}
|
|
1353
|
-
this.logger
|
|
1430
|
+
this.logger.debug("GChat API: spaces.messages.reactions.delete", {
|
|
1354
1431
|
reactionName: reaction.name
|
|
1355
1432
|
});
|
|
1356
1433
|
await this.chatApi.spaces.messages.reactions.delete({
|
|
1357
1434
|
name: reaction.name
|
|
1358
1435
|
});
|
|
1359
|
-
this.logger
|
|
1436
|
+
this.logger.debug(
|
|
1360
1437
|
"GChat API: spaces.messages.reactions.delete response",
|
|
1361
1438
|
{
|
|
1362
1439
|
ok: true
|
|
@@ -1379,12 +1456,12 @@ var GoogleChatAdapter = class {
|
|
|
1379
1456
|
*/
|
|
1380
1457
|
async openDM(userId) {
|
|
1381
1458
|
try {
|
|
1382
|
-
this.logger
|
|
1459
|
+
this.logger.debug("GChat API: spaces.findDirectMessage", { userId });
|
|
1383
1460
|
const findResponse = await this.chatApi.spaces.findDirectMessage({
|
|
1384
1461
|
name: userId
|
|
1385
1462
|
});
|
|
1386
1463
|
if (findResponse.data.name) {
|
|
1387
|
-
this.logger
|
|
1464
|
+
this.logger.debug("GChat API: Found existing DM space", {
|
|
1388
1465
|
spaceName: findResponse.data.name
|
|
1389
1466
|
});
|
|
1390
1467
|
return this.encodeThreadId({
|
|
@@ -1395,17 +1472,17 @@ var GoogleChatAdapter = class {
|
|
|
1395
1472
|
} catch (error) {
|
|
1396
1473
|
const gError = error;
|
|
1397
1474
|
if (gError.code !== 404) {
|
|
1398
|
-
this.logger
|
|
1475
|
+
this.logger.debug("GChat API: findDirectMessage failed", { error });
|
|
1399
1476
|
}
|
|
1400
1477
|
}
|
|
1401
1478
|
const chatApi = this.impersonatedChatApi || this.chatApi;
|
|
1402
1479
|
if (!this.impersonatedChatApi) {
|
|
1403
|
-
this.logger
|
|
1480
|
+
this.logger.warn(
|
|
1404
1481
|
"openDM: No existing DM found and no impersonation configured. Creating new DMs requires domain-wide delegation. Set 'impersonateUser' in adapter config."
|
|
1405
1482
|
);
|
|
1406
1483
|
}
|
|
1407
1484
|
try {
|
|
1408
|
-
this.logger
|
|
1485
|
+
this.logger.debug("GChat API: spaces.setup (DM)", {
|
|
1409
1486
|
userId,
|
|
1410
1487
|
hasImpersonation: !!this.impersonatedChatApi,
|
|
1411
1488
|
impersonateUser: this.impersonateUser
|
|
@@ -1427,9 +1504,12 @@ var GoogleChatAdapter = class {
|
|
|
1427
1504
|
});
|
|
1428
1505
|
const spaceName = response.data.name;
|
|
1429
1506
|
if (!spaceName) {
|
|
1430
|
-
throw new
|
|
1507
|
+
throw new NetworkError(
|
|
1508
|
+
"gchat",
|
|
1509
|
+
"Failed to create DM - no space name returned"
|
|
1510
|
+
);
|
|
1431
1511
|
}
|
|
1432
|
-
this.logger
|
|
1512
|
+
this.logger.debug("GChat API: spaces.setup response", { spaceName });
|
|
1433
1513
|
return this.encodeThreadId({ spaceName, isDM: true });
|
|
1434
1514
|
} catch (error) {
|
|
1435
1515
|
this.handleGoogleChatError(error, "openDM");
|
|
@@ -1470,7 +1550,7 @@ var GoogleChatAdapter = class {
|
|
|
1470
1550
|
* to get the most recent messages, then reverse for chronological order within page.
|
|
1471
1551
|
*/
|
|
1472
1552
|
async fetchMessagesBackward(api, spaceName, threadId, filter, limit, cursor) {
|
|
1473
|
-
this.logger
|
|
1553
|
+
this.logger.debug("GChat API: spaces.messages.list (backward)", {
|
|
1474
1554
|
spaceName,
|
|
1475
1555
|
filter,
|
|
1476
1556
|
pageSize: limit,
|
|
@@ -1485,7 +1565,7 @@ var GoogleChatAdapter = class {
|
|
|
1485
1565
|
// Get newest messages first
|
|
1486
1566
|
});
|
|
1487
1567
|
const rawMessages = (response.data.messages || []).reverse();
|
|
1488
|
-
this.logger
|
|
1568
|
+
this.logger.debug("GChat API: spaces.messages.list response (backward)", {
|
|
1489
1569
|
messageCount: rawMessages.length,
|
|
1490
1570
|
hasNextPageToken: !!response.data.nextPageToken
|
|
1491
1571
|
});
|
|
@@ -1512,7 +1592,7 @@ var GoogleChatAdapter = class {
|
|
|
1512
1592
|
* as it requires fetching all messages to find the cursor position.
|
|
1513
1593
|
*/
|
|
1514
1594
|
async fetchMessagesForward(api, spaceName, threadId, filter, limit, cursor) {
|
|
1515
|
-
this.logger
|
|
1595
|
+
this.logger.debug("GChat API: spaces.messages.list (forward)", {
|
|
1516
1596
|
spaceName,
|
|
1517
1597
|
filter,
|
|
1518
1598
|
limit,
|
|
@@ -1533,7 +1613,7 @@ var GoogleChatAdapter = class {
|
|
|
1533
1613
|
allRawMessages.push(...pageMessages);
|
|
1534
1614
|
pageToken = response.data.nextPageToken ?? void 0;
|
|
1535
1615
|
} while (pageToken);
|
|
1536
|
-
this.logger
|
|
1616
|
+
this.logger.debug(
|
|
1537
1617
|
"GChat API: fetched all messages for forward pagination",
|
|
1538
1618
|
{
|
|
1539
1619
|
totalCount: allRawMessages.length
|
|
@@ -1580,9 +1660,11 @@ var GoogleChatAdapter = class {
|
|
|
1580
1660
|
});
|
|
1581
1661
|
const msgIsBot = msg.sender?.type === "BOT";
|
|
1582
1662
|
const userId = msg.sender?.name || "unknown";
|
|
1583
|
-
const displayName = await this.
|
|
1663
|
+
const displayName = await this.userInfoCache.resolveDisplayName(
|
|
1584
1664
|
userId,
|
|
1585
|
-
msg.sender?.displayName ?? void 0
|
|
1665
|
+
msg.sender?.displayName ?? void 0,
|
|
1666
|
+
this.botUserId,
|
|
1667
|
+
this.userName
|
|
1586
1668
|
);
|
|
1587
1669
|
const isMe = this.isMessageFromSelf(msg);
|
|
1588
1670
|
return {
|
|
@@ -1608,9 +1690,9 @@ var GoogleChatAdapter = class {
|
|
|
1608
1690
|
async fetchThread(threadId) {
|
|
1609
1691
|
const { spaceName } = this.decodeThreadId(threadId);
|
|
1610
1692
|
try {
|
|
1611
|
-
this.logger
|
|
1693
|
+
this.logger.debug("GChat API: spaces.get", { spaceName });
|
|
1612
1694
|
const response = await this.chatApi.spaces.get({ name: spaceName });
|
|
1613
|
-
this.logger
|
|
1695
|
+
this.logger.debug("GChat API: spaces.get response", {
|
|
1614
1696
|
displayName: response.data.displayName
|
|
1615
1697
|
});
|
|
1616
1698
|
return {
|
|
@@ -1626,34 +1708,22 @@ var GoogleChatAdapter = class {
|
|
|
1626
1708
|
}
|
|
1627
1709
|
}
|
|
1628
1710
|
encodeThreadId(platformData) {
|
|
1629
|
-
|
|
1630
|
-
const dmPart = platformData.isDM ? ":dm" : "";
|
|
1631
|
-
return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;
|
|
1711
|
+
return encodeThreadId(platformData);
|
|
1632
1712
|
}
|
|
1633
1713
|
/**
|
|
1634
1714
|
* Check if a thread is a direct message conversation.
|
|
1635
|
-
* Checks for the :dm marker in the thread ID which is set when
|
|
1636
|
-
* processing DM messages or opening DMs.
|
|
1637
1715
|
*/
|
|
1638
1716
|
isDM(threadId) {
|
|
1639
|
-
return threadId
|
|
1717
|
+
return isDMThread(threadId);
|
|
1640
1718
|
}
|
|
1641
1719
|
decodeThreadId(threadId) {
|
|
1642
|
-
|
|
1643
|
-
const cleanId = isDM ? threadId.slice(0, -3) : threadId;
|
|
1644
|
-
const parts = cleanId.split(":");
|
|
1645
|
-
if (parts.length < 2 || parts[0] !== "gchat") {
|
|
1646
|
-
throw new Error(`Invalid Google Chat thread ID: ${threadId}`);
|
|
1647
|
-
}
|
|
1648
|
-
const spaceName = parts[1];
|
|
1649
|
-
const threadName = parts[2] ? Buffer.from(parts[2], "base64url").toString("utf-8") : void 0;
|
|
1650
|
-
return { spaceName, threadName, isDM };
|
|
1720
|
+
return decodeThreadId(threadId);
|
|
1651
1721
|
}
|
|
1652
1722
|
parseMessage(raw) {
|
|
1653
1723
|
const event = raw;
|
|
1654
1724
|
const messagePayload = event.chat?.messagePayload;
|
|
1655
1725
|
if (!messagePayload) {
|
|
1656
|
-
throw new
|
|
1726
|
+
throw new ValidationError2("gchat", "Cannot parse non-message event");
|
|
1657
1727
|
}
|
|
1658
1728
|
const threadName = messagePayload.message.thread?.name || messagePayload.message.name;
|
|
1659
1729
|
const threadId = this.encodeThreadId({
|
|
@@ -1681,11 +1751,11 @@ var GoogleChatAdapter = class {
|
|
|
1681
1751
|
const botDisplayName = botUser.displayName;
|
|
1682
1752
|
if (botUser.name && !this.botUserId) {
|
|
1683
1753
|
this.botUserId = botUser.name;
|
|
1684
|
-
this.logger
|
|
1754
|
+
this.logger.info("Learned bot user ID from mention", {
|
|
1685
1755
|
botUserId: this.botUserId
|
|
1686
1756
|
});
|
|
1687
1757
|
this.state?.set("gchat:botUserId", this.botUserId).catch(
|
|
1688
|
-
(err) => this.logger
|
|
1758
|
+
(err) => this.logger.error("Failed to persist botUserId", { error: err })
|
|
1689
1759
|
);
|
|
1690
1760
|
}
|
|
1691
1761
|
if (annotation.startIndex !== void 0 && annotation.length !== void 0) {
|
|
@@ -1693,7 +1763,7 @@ var GoogleChatAdapter = class {
|
|
|
1693
1763
|
const length = annotation.length;
|
|
1694
1764
|
const mentionText = text.slice(startIndex, startIndex + length);
|
|
1695
1765
|
text = text.slice(0, startIndex) + `@${this.userName}` + text.slice(startIndex + length);
|
|
1696
|
-
this.logger
|
|
1766
|
+
this.logger.debug("Normalized bot mention", {
|
|
1697
1767
|
original: mentionText,
|
|
1698
1768
|
replacement: `@${this.userName}`
|
|
1699
1769
|
});
|
|
@@ -1721,77 +1791,23 @@ var GoogleChatAdapter = class {
|
|
|
1721
1791
|
return senderId === this.botUserId;
|
|
1722
1792
|
}
|
|
1723
1793
|
if (!this.botUserId && message.sender?.type === "BOT") {
|
|
1724
|
-
this.logger
|
|
1794
|
+
this.logger.debug(
|
|
1725
1795
|
"Cannot determine isMe - bot user ID not yet learned. Bot ID is learned from @mentions. Assuming message is not from self.",
|
|
1726
1796
|
{ senderId }
|
|
1727
1797
|
);
|
|
1728
1798
|
}
|
|
1729
1799
|
return false;
|
|
1730
1800
|
}
|
|
1731
|
-
/**
|
|
1732
|
-
* Cache user info for later lookup (e.g., when processing Pub/Sub messages).
|
|
1733
|
-
*/
|
|
1734
|
-
async cacheUserInfo(userId, displayName, email) {
|
|
1735
|
-
if (!displayName || displayName === "unknown") return;
|
|
1736
|
-
const userInfo = { displayName, email };
|
|
1737
|
-
this.userInfoCache.set(userId, userInfo);
|
|
1738
|
-
if (this.state) {
|
|
1739
|
-
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
1740
|
-
await this.state.set(
|
|
1741
|
-
cacheKey,
|
|
1742
|
-
userInfo,
|
|
1743
|
-
USER_INFO_CACHE_TTL_MS
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
/**
|
|
1748
|
-
* Get cached user info. Checks in-memory cache first, then falls back to state adapter.
|
|
1749
|
-
*/
|
|
1750
|
-
async getCachedUserInfo(userId) {
|
|
1751
|
-
const inMemory = this.userInfoCache.get(userId);
|
|
1752
|
-
if (inMemory) {
|
|
1753
|
-
return inMemory;
|
|
1754
|
-
}
|
|
1755
|
-
if (!this.state) return null;
|
|
1756
|
-
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
1757
|
-
const fromState = await this.state.get(cacheKey);
|
|
1758
|
-
if (fromState) {
|
|
1759
|
-
this.userInfoCache.set(userId, fromState);
|
|
1760
|
-
}
|
|
1761
|
-
return fromState;
|
|
1762
|
-
}
|
|
1763
|
-
/**
|
|
1764
|
-
* Resolve user display name, using cache if available.
|
|
1765
|
-
*/
|
|
1766
|
-
async resolveUserDisplayName(userId, providedDisplayName) {
|
|
1767
|
-
if (providedDisplayName && providedDisplayName !== "unknown") {
|
|
1768
|
-
this.cacheUserInfo(userId, providedDisplayName).catch(() => {
|
|
1769
|
-
});
|
|
1770
|
-
return providedDisplayName;
|
|
1771
|
-
}
|
|
1772
|
-
if (this.botUserId && userId === this.botUserId) {
|
|
1773
|
-
return this.userName;
|
|
1774
|
-
}
|
|
1775
|
-
const cached = await this.getCachedUserInfo(userId);
|
|
1776
|
-
if (cached?.displayName) {
|
|
1777
|
-
return cached.displayName;
|
|
1778
|
-
}
|
|
1779
|
-
return userId.replace("users/", "User ");
|
|
1780
|
-
}
|
|
1781
1801
|
handleGoogleChatError(error, context) {
|
|
1782
1802
|
const gError = error;
|
|
1783
|
-
this.logger
|
|
1803
|
+
this.logger.error(`GChat API error${context ? ` (${context})` : ""}`, {
|
|
1784
1804
|
code: gError.code,
|
|
1785
1805
|
message: gError.message,
|
|
1786
1806
|
errors: gError.errors,
|
|
1787
1807
|
error
|
|
1788
1808
|
});
|
|
1789
1809
|
if (gError.code === 429) {
|
|
1790
|
-
throw new
|
|
1791
|
-
"Google Chat rate limit exceeded",
|
|
1792
|
-
void 0,
|
|
1793
|
-
error
|
|
1794
|
-
);
|
|
1810
|
+
throw new AdapterRateLimitError("gchat");
|
|
1795
1811
|
}
|
|
1796
1812
|
throw error;
|
|
1797
1813
|
}
|