mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
b50822aab5
* refactor(googlechat): privatize internal test seams * refactor(matrix): remove internal test exports * chore(deadcode): refresh export baseline
131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
import { buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/channel-core";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
// Googlechat plugin module implements targets behavior.
|
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import { resolveGoogleChatAccount, type ResolvedGoogleChatAccount } from "./accounts.js";
|
|
import { findGoogleChatDirectMessage, getGoogleChatSpace } from "./api.js";
|
|
import type { GoogleChatSpace } from "./types.js";
|
|
|
|
export function normalizeGoogleChatTarget(raw?: string | null): string | undefined {
|
|
const trimmed = raw?.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
const withoutPrefix = trimmed.replace(/^(googlechat|google-chat|gchat):/i, "");
|
|
const normalized = withoutPrefix
|
|
.replace(/^user:(users\/)?/i, "users/")
|
|
.replace(/^space:(spaces\/)?/i, "spaces/");
|
|
if (isGoogleChatUserTarget(normalized)) {
|
|
const suffix = normalized.slice("users/".length);
|
|
return suffix.includes("@") ? `users/${normalizeLowercaseStringOrEmpty(suffix)}` : normalized;
|
|
}
|
|
if (isGoogleChatSpaceTarget(normalized)) {
|
|
return normalized;
|
|
}
|
|
if (normalized.includes("@")) {
|
|
return `users/${normalizeLowercaseStringOrEmpty(normalized)}`;
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function isGoogleChatUserTarget(value: string): boolean {
|
|
return normalizeLowercaseStringOrEmpty(value).startsWith("users/");
|
|
}
|
|
|
|
export function isGoogleChatSpaceTarget(value: string): boolean {
|
|
return normalizeLowercaseStringOrEmpty(value).startsWith("spaces/");
|
|
}
|
|
|
|
function resolveGoogleChatSpaceChatType(space: GoogleChatSpace): "direct" | "group" | undefined {
|
|
const spaceType = (space.spaceType ?? "").toUpperCase();
|
|
// The current field wins when both current and deprecated fields are present.
|
|
if (spaceType === "DIRECT_MESSAGE") {
|
|
return "direct";
|
|
}
|
|
if (spaceType === "SPACE" || spaceType === "GROUP_CHAT") {
|
|
return "group";
|
|
}
|
|
if (space.singleUserBotDm === true || (space.type ?? "").toUpperCase() === "DM") {
|
|
return "direct";
|
|
}
|
|
if ((space.type ?? "").toUpperCase() === "ROOM") {
|
|
return "group";
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function isGoogleChatGroupSpace(space: GoogleChatSpace): boolean {
|
|
// Legacy webhook payloads can omit type metadata. Preserve their historical
|
|
// group default while outbound routing requires an exact API classification.
|
|
return resolveGoogleChatSpaceChatType(space) !== "direct";
|
|
}
|
|
|
|
function stripMessageSuffix(target: string): string {
|
|
const index = target.indexOf("/messages/");
|
|
if (index === -1) {
|
|
return target;
|
|
}
|
|
return target.slice(0, index);
|
|
}
|
|
|
|
export async function resolveGoogleChatOutboundSpace(params: {
|
|
account: ResolvedGoogleChatAccount;
|
|
target: string;
|
|
}): Promise<string> {
|
|
const normalized = normalizeGoogleChatTarget(params.target);
|
|
if (!normalized) {
|
|
throw new Error("Missing Google Chat target.");
|
|
}
|
|
const base = stripMessageSuffix(normalized);
|
|
if (isGoogleChatSpaceTarget(base)) {
|
|
return base;
|
|
}
|
|
if (isGoogleChatUserTarget(base)) {
|
|
const dm = await findGoogleChatDirectMessage({
|
|
account: params.account,
|
|
userName: base,
|
|
});
|
|
if (!dm?.name) {
|
|
throw new Error(`No Google Chat DM found for ${base}`);
|
|
}
|
|
return dm.name;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
export async function resolveGoogleChatOutboundSessionRoute(params: {
|
|
cfg: OpenClawConfig;
|
|
agentId: string;
|
|
accountId?: string | null;
|
|
target: string;
|
|
}) {
|
|
const account = resolveGoogleChatAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
const spaceName = await resolveGoogleChatOutboundSpace({ account, target: params.target });
|
|
if (!isGoogleChatSpaceTarget(spaceName)) {
|
|
return null;
|
|
}
|
|
let space: GoogleChatSpace;
|
|
try {
|
|
space = await getGoogleChatSpace({ account, spaceName });
|
|
} catch {
|
|
// Space classification only enriches session routing. Delivery must remain
|
|
// available when this auxiliary read is unavailable or lacks permission.
|
|
return null;
|
|
}
|
|
const chatType = resolveGoogleChatSpaceChatType(space);
|
|
if (!chatType) {
|
|
return null;
|
|
}
|
|
return buildChannelOutboundSessionRoute({
|
|
cfg: params.cfg,
|
|
agentId: params.agentId,
|
|
channel: "googlechat",
|
|
accountId: params.accountId,
|
|
recipientSessionExact: true,
|
|
peer: { kind: chatType, id: spaceName },
|
|
chatType,
|
|
from: `googlechat:${spaceName}`,
|
|
to: spaceName,
|
|
});
|
|
}
|