feat(googlechat): add webhook server and startup script
- Add run-webhook.ts for handling Google Chat messages via HTTP webhook - Add webhook-server.ts for full Clawdbot integration - Add start-googlechat.sh startup script for easy launching Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8bf4401bd8
commit
ffb10c898f
101
src/googlechat/run-webhook.ts
Normal file
101
src/googlechat/run-webhook.ts
Normal file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
import express, { type Request, type Response } from "express";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const PORT = 18792;
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Health check
|
||||
app.get("/health", (_req: Request, res: Response) => {
|
||||
res.json({ ok: true, provider: "googlechat" });
|
||||
});
|
||||
|
||||
// Google Chat webhook
|
||||
app.post("/webhook/googlechat", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const event = req.body;
|
||||
const chat = event.chat || {};
|
||||
|
||||
// Detect event type from payload structure
|
||||
const isAddedToSpace = !!chat.addedToSpacePayload;
|
||||
const isMessage = !!chat.messagePayload;
|
||||
|
||||
const eventType = isAddedToSpace ? "ADDED_TO_SPACE" : isMessage ? "MESSAGE" : "UNKNOWN";
|
||||
console.log(`[googlechat] Received event: ${eventType}`);
|
||||
|
||||
if (isAddedToSpace) {
|
||||
const user = chat.user?.displayName || "there";
|
||||
res.json({
|
||||
hostAppDataAction: {
|
||||
chatDataAction: {
|
||||
createMessageAction: {
|
||||
message: {
|
||||
text: `Hello ${user}! I'm Clawdbot, your AI assistant. Send me a message and I'll respond!`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMessage) {
|
||||
const msg = chat.messagePayload.message;
|
||||
const senderName = msg?.sender?.displayName || "Unknown";
|
||||
const text = msg?.argumentText || msg?.text || "";
|
||||
const spaceId = msg?.space?.name?.replace("spaces/", "") || "default";
|
||||
|
||||
console.log(`[googlechat] Message from ${senderName}: ${text}`);
|
||||
|
||||
let responseText: string;
|
||||
try {
|
||||
// Use clawdbot CLI to get AI response
|
||||
// Escape the text for shell
|
||||
const escapedText = text.replace(/'/g, "'\\''");
|
||||
const sessionId = `googlechat:${spaceId}`;
|
||||
const result = execSync(
|
||||
`clawdbot agent --message '${escapedText}' --session-id '${sessionId}' --local 2>&1`,
|
||||
{
|
||||
timeout: 25000, // 25 second timeout (Google Chat times out at ~30s)
|
||||
encoding: 'utf-8',
|
||||
maxBuffer: 1024 * 1024,
|
||||
}
|
||||
);
|
||||
responseText = result.trim() || "I processed your message but have no response.";
|
||||
console.log(`[googlechat] AI Response: ${responseText.slice(0, 100)}...`);
|
||||
} catch (err: any) {
|
||||
console.error(`[googlechat] CLI error:`, err.message);
|
||||
if (err.killed) {
|
||||
responseText = "Sorry, the request timed out. Please try a simpler question.";
|
||||
} else {
|
||||
responseText = "Sorry, I encountered an error processing your message.";
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
hostAppDataAction: {
|
||||
chatDataAction: {
|
||||
createMessageAction: {
|
||||
message: {
|
||||
text: responseText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({});
|
||||
} catch (error) {
|
||||
console.error("[googlechat] Error:", error);
|
||||
res.status(500).json({ error: "Internal error" });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[googlechat] Webhook server running on port ${PORT}`);
|
||||
console.log(`[googlechat] Local: http://localhost:${PORT}/webhook/googlechat`);
|
||||
console.log(`[googlechat] Use ngrok URL + /webhook/googlechat for Google Chat config`);
|
||||
});
|
||||
155
src/googlechat/webhook-server.ts
Normal file
155
src/googlechat/webhook-server.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import express, { type Request, type Response } from "express";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
import type { GoogleChatEvent } from "./types.js";
|
||||
|
||||
export type GoogleChatWebhookOptions = {
|
||||
account: ResolvedGoogleChatAccount;
|
||||
config: ClawdbotConfig;
|
||||
runtime: RuntimeEnv;
|
||||
port?: number;
|
||||
onMessage?: (event: GoogleChatEvent) => Promise<void>;
|
||||
};
|
||||
|
||||
export type NormalizedGoogleChatMessage = {
|
||||
provider: "googlechat";
|
||||
accountId: string;
|
||||
messageId: string;
|
||||
timestamp: number;
|
||||
sender: {
|
||||
id: string;
|
||||
name: string;
|
||||
email?: string;
|
||||
};
|
||||
chat: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "dm" | "group";
|
||||
};
|
||||
thread?: {
|
||||
id: string;
|
||||
};
|
||||
content: {
|
||||
text: string;
|
||||
};
|
||||
raw: GoogleChatEvent;
|
||||
};
|
||||
|
||||
function normalizeMessage(
|
||||
event: GoogleChatEvent,
|
||||
accountId: string,
|
||||
): NormalizedGoogleChatMessage | null {
|
||||
if (event.type !== "MESSAGE" || !event.message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const msg = event.message;
|
||||
|
||||
// Skip messages from bots
|
||||
if (msg.sender.type === "BOT") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const spaceId = msg.space.name.replace("spaces/", "");
|
||||
const isDM = msg.space.type === "DM";
|
||||
|
||||
return {
|
||||
provider: "googlechat",
|
||||
accountId,
|
||||
messageId: msg.name,
|
||||
timestamp: new Date(msg.createTime).getTime(),
|
||||
sender: {
|
||||
id: msg.sender.name.replace("users/", ""),
|
||||
name: msg.sender.displayName,
|
||||
email: msg.sender.email,
|
||||
},
|
||||
chat: {
|
||||
id: spaceId,
|
||||
name: msg.space.displayName || spaceId,
|
||||
type: isDM ? "dm" : "group",
|
||||
},
|
||||
thread: msg.thread
|
||||
? {
|
||||
id: msg.thread.name,
|
||||
}
|
||||
: undefined,
|
||||
content: {
|
||||
text: msg.argumentText || msg.text || "",
|
||||
},
|
||||
raw: event,
|
||||
};
|
||||
}
|
||||
|
||||
export async function startGoogleChatWebhookServer(
|
||||
options: GoogleChatWebhookOptions,
|
||||
): Promise<{ server: ReturnType<typeof express>; port: number; stop: () => void }> {
|
||||
const { account, port = 18792 } = options;
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Health check endpoint
|
||||
app.get("/health", (_req: Request, res: Response) => {
|
||||
res.json({ ok: true, provider: "googlechat", accountId: account.accountId });
|
||||
});
|
||||
|
||||
// Google Chat webhook endpoint
|
||||
app.post("/webhook/googlechat", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const event = req.body as GoogleChatEvent;
|
||||
|
||||
console.log(`[googlechat:${account.accountId}] Received event: ${event.type}`);
|
||||
|
||||
// Handle different event types
|
||||
if (event.type === "ADDED_TO_SPACE") {
|
||||
// Bot was added to a space
|
||||
res.json({
|
||||
text: "Hello! I'm Clawdbot, your personal AI assistant. Send me a message to get started!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === "MESSAGE") {
|
||||
const normalized = normalizeMessage(event, account.accountId);
|
||||
|
||||
if (normalized) {
|
||||
console.log(
|
||||
`[googlechat:${account.accountId}] Message from ${normalized.sender.name}: ${normalized.content.text.slice(0, 50)}...`,
|
||||
);
|
||||
|
||||
// Call the message handler if provided
|
||||
if (options.onMessage) {
|
||||
await options.onMessage(event);
|
||||
}
|
||||
|
||||
// For now, echo back a confirmation
|
||||
// In full integration, this would route to the agent
|
||||
res.json({
|
||||
text: `Got your message! (Clawdbot Google Chat integration is working)\n\nYou said: "${normalized.content.text}"`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Default response for other events
|
||||
res.json({});
|
||||
} catch (error) {
|
||||
console.error(`[googlechat:${account.accountId}] Webhook error:`, error);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Start server
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`[googlechat:${account.accountId}] Webhook server listening on port ${port}`);
|
||||
console.log(`[googlechat:${account.accountId}] Webhook URL: http://localhost:${port}/webhook/googlechat`);
|
||||
});
|
||||
|
||||
const stop = () => {
|
||||
server.close();
|
||||
console.log(`[googlechat:${account.accountId}] Webhook server stopped`);
|
||||
};
|
||||
|
||||
return { server: app, port, stop };
|
||||
}
|
||||
56
start-googlechat.sh
Executable file
56
start-googlechat.sh
Executable file
@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
# Start Clawdbot Google Chat Integration
|
||||
# Run this script after restarting your computer
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "Starting Clawdbot Google Chat..."
|
||||
echo ""
|
||||
|
||||
# Kill any existing processes
|
||||
pkill -f "ngrok http 18792" 2>/dev/null
|
||||
pkill -f "run-webhook" 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
# Start ngrok in background
|
||||
echo "1. Starting ngrok tunnel..."
|
||||
ngrok http 18792 > /tmp/ngrok.log 2>&1 &
|
||||
sleep 4
|
||||
|
||||
# Get the ngrok URL
|
||||
NGROK_URL=$(curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"https://[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$NGROK_URL" ]; then
|
||||
echo " ERROR: ngrok failed to start. Check your internet connection."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " ngrok URL: $NGROK_URL"
|
||||
echo ""
|
||||
|
||||
# Start webhook server
|
||||
echo "2. Starting webhook server..."
|
||||
npx tsx src/googlechat/run-webhook.ts > /tmp/googlechat-webhook.log 2>&1 &
|
||||
sleep 3
|
||||
|
||||
# Verify webhook is running
|
||||
if lsof -i :18792 > /dev/null 2>&1; then
|
||||
echo " Webhook running on port 18792"
|
||||
else
|
||||
echo " ERROR: Webhook failed to start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Clawdbot Google Chat is READY!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Webhook URL: ${NGROK_URL}/webhook/googlechat"
|
||||
echo ""
|
||||
echo "NOTE: If the ngrok URL changed, update it in Google Chat:"
|
||||
echo " https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat"
|
||||
echo ""
|
||||
echo "Logs: tail -f /tmp/googlechat-webhook.log"
|
||||
echo ""
|
||||
echo "To stop: pkill -f ngrok && pkill -f run-webhook"
|
||||
Loading…
Reference in New Issue
Block a user