From 0af91c6dd7aeb2fade3ae98e039b9c65f9755f0d Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:41:25 +0000 Subject: [PATCH 1/2] Add Codegen Google Meet + Zoom Bot proof-of-concept examples --- examples/meeting_bot/README.md | 91 ++++++ examples/meeting_bot/recall_ai_poc.py | 245 +++++++++++++++ examples/meeting_bot/zoom_sdk_poc.js | 427 ++++++++++++++++++++++++++ 3 files changed, 763 insertions(+) create mode 100644 examples/meeting_bot/README.md create mode 100644 examples/meeting_bot/recall_ai_poc.py create mode 100644 examples/meeting_bot/zoom_sdk_poc.js diff --git a/examples/meeting_bot/README.md b/examples/meeting_bot/README.md new file mode 100644 index 000000000..b2ad216e2 --- /dev/null +++ b/examples/meeting_bot/README.md @@ -0,0 +1,91 @@ +# Codegen Meeting Bot Examples + +This directory contains proof-of-concept examples for implementing a Codegen bot that can join Google Meet and Zoom meetings. + +## Overview + +The Codegen Meeting Bot is designed to: + +1. Join video meetings on platforms like Google Meet and Zoom +2. Record and transcribe meetings +3. Answer questions about Codegen during meetings +4. Generate meeting summaries and action items + +## Implementation Options + +### 1. Recall.ai Integration (recall_ai_poc.py) + +This example demonstrates how to use the [Recall.ai](https://www.recall.ai/) API to create a meeting bot that works across multiple platforms. + +**Features:** +- Platform-agnostic implementation (works with Google Meet, Zoom, MS Teams, etc.) +- Real-time transcription streaming +- Meeting recording and processing +- Customizable bot name and appearance + +**Requirements:** +- Python 3.8+ +- Recall.ai API key +- Required packages: `requests`, `asyncio`, `websockets` + +**Usage:** +```python +# Set your Recall.ai API key +export RECALL_API_KEY="your_api_key_here" + +# Run the example +python recall_ai_poc.py +``` + +### 2. Zoom SDK Integration (zoom_sdk_poc.js) + +This example demonstrates how to use the Zoom Meeting SDK to create a bot specifically for Zoom meetings. + +**Features:** +- Join Zoom meetings programmatically +- Listen to meeting events (chat messages, participant changes, etc.) +- Respond to questions in the chat +- Customizable bot behavior + +**Requirements:** +- Node.js +- Zoom Meeting SDK credentials +- Required packages: `puppeteer`, `express`, `body-parser`, `crypto`, `cors` + +**Usage:** +```javascript +// Set your Zoom SDK credentials +export ZOOM_SDK_KEY="your_sdk_key_here" +export ZOOM_SDK_SECRET="your_sdk_secret_here" + +// Install dependencies +npm install puppeteer express body-parser crypto cors + +// Run the example +node zoom_sdk_poc.js +``` + +## Integration with Codegen + +To fully integrate these examples with Codegen, you would need to: + +1. Connect to the Codegen API to process questions and generate responses +2. Implement a calendar integration to automatically schedule bots for meetings +3. Create a user interface for managing bot settings and viewing meeting summaries +4. Set up secure storage for meeting recordings and transcripts + +## Next Steps + +These examples are proof-of-concept implementations and not production-ready. For a production implementation, consider: + +1. Error handling and retry logic +2. Authentication and security +3. Scalability for multiple concurrent meetings +4. Proper logging and monitoring +5. User management and permissions + +## Resources + +- [Recall.ai Documentation](https://docs.recall.ai/docs/getting-started) +- [Zoom Meeting SDK Documentation](https://marketplace.zoom.us/docs/sdk/native-sdks/web/) +- [Google Meet Bot Examples](https://github.com/Ritika-Das/Google-Meet-Bot) \ No newline at end of file diff --git a/examples/meeting_bot/recall_ai_poc.py b/examples/meeting_bot/recall_ai_poc.py new file mode 100644 index 000000000..901fdebba --- /dev/null +++ b/examples/meeting_bot/recall_ai_poc.py @@ -0,0 +1,245 @@ +""" +Codegen Meeting Bot - Recall.ai Proof of Concept + +This script demonstrates how to use Recall.ai to create a meeting bot that can: +1. Join a Google Meet or Zoom meeting +2. Record the meeting +3. Generate a transcript +4. Process the transcript with Codegen + +Requirements: +- Recall.ai API key +- Python 3.8+ +- Required packages: requests, asyncio, websockets + +Note: This is a proof-of-concept and not production-ready code. +""" + +import os +import json +import time +import asyncio +import requests +from typing import Dict, Any, Optional, List + +# Configuration +RECALL_API_KEY = os.environ.get("RECALL_API_KEY", "your_api_key_here") +RECALL_API_BASE_URL = "https://api.recall.ai/api/v1" + +class CodegenMeetingBot: + """A meeting bot that uses Recall.ai to join meetings and process transcripts with Codegen.""" + + def __init__(self, api_key: str): + """Initialize the meeting bot with the Recall.ai API key.""" + self.api_key = api_key + self.headers = { + "Authorization": f"Token {self.api_key}", + "Content-Type": "application/json" + } + + def create_bot(self, + platform: str, + meeting_url: str, + bot_name: str = "Codegen Assistant", + join_at: Optional[str] = None) -> Dict[str, Any]: + """ + Create a bot to join a meeting. + + Args: + platform: The meeting platform ("zoom", "google_meet", "ms_teams", etc.) + meeting_url: The URL of the meeting to join + bot_name: The display name for the bot + join_at: ISO 8601 timestamp for when the bot should join (None for immediate) + + Returns: + The bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/" + + payload = { + "meeting_url": meeting_url, + "platform": platform, + "bot_name": bot_name + } + + if join_at: + payload["join_at"] = join_at + + response = requests.post(endpoint, headers=self.headers, json=payload) + response.raise_for_status() + + return response.json() + + def get_bot(self, bot_id: str) -> Dict[str, Any]: + """ + Get information about a bot. + + Args: + bot_id: The ID of the bot + + Returns: + The bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/{bot_id}/" + + response = requests.get(endpoint, headers=self.headers) + response.raise_for_status() + + return response.json() + + def list_bots(self, limit: int = 10) -> List[Dict[str, Any]]: + """ + List all bots. + + Args: + limit: Maximum number of bots to return + + Returns: + List of bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/?limit={limit}" + + response = requests.get(endpoint, headers=self.headers) + response.raise_for_status() + + return response.json().get("results", []) + + async def stream_transcription(self, bot_id: str): + """ + Stream real-time transcription from a bot. + + Args: + bot_id: The ID of the bot + """ + import websockets + + # Get the bot data to check if it's active + bot = self.get_bot(bot_id) + + if bot.get("status") != "joined": + print(f"Bot is not active in the meeting. Current status: {bot.get('status')}") + return + + # Connect to the transcription websocket + ws_url = f"wss://api.recall.ai/ws/bot/{bot_id}/transcription/" + + async with websockets.connect(ws_url, extra_headers={"Authorization": f"Token {self.api_key}"}) as websocket: + print("Connected to transcription stream. Waiting for transcription...") + + while True: + try: + message = await websocket.recv() + data = json.loads(message) + + if data.get("event") == "transcript_part": + speaker = data.get("speaker", "Unknown") + text = data.get("text", "") + print(f"{speaker}: {text}") + + # Here you would process the transcript with Codegen + # For example, detect if someone is asking a question about Codegen + if "codegen" in text.lower() and "?" in text: + print("Detected question about Codegen!") + # In a real implementation, you would: + # 1. Process the question with Codegen + # 2. Generate a response + # 3. Send the response back to the meeting (via chat or audio) + + except Exception as e: + print(f"Error in transcription stream: {e}") + break + + def process_meeting_recording(self, bot_id: str) -> Dict[str, Any]: + """ + Process a completed meeting recording. + + Args: + bot_id: The ID of the bot + + Returns: + Processed meeting data + """ + # Get the bot data + bot = self.get_bot(bot_id) + + if bot.get("status") not in ["left", "ended"]: + print(f"Meeting is not completed yet. Current status: {bot.get('status')}") + return {} + + # Get the recording URL + recording_url = bot.get("video_url") + + if not recording_url: + print("No recording available for this meeting.") + return {} + + print(f"Recording available at: {recording_url}") + + # In a real implementation, you would: + # 1. Download the recording + # 2. Process the full transcript + # 3. Generate a meeting summary with Codegen + # 4. Extract action items + # 5. Store the results + + return { + "meeting_id": bot.get("id"), + "duration": bot.get("duration"), + "recording_url": recording_url, + "platform": bot.get("platform"), + "summary": "This is where the meeting summary would go.", + "action_items": ["Action item 1", "Action item 2"] + } + + +async def main(): + """Main function to demonstrate the Codegen Meeting Bot.""" + # Initialize the bot + bot = CodegenMeetingBot(RECALL_API_KEY) + + # Example: Create a bot to join a Google Meet meeting + meeting_url = "https://meet.google.com/abc-defg-hij" + + try: + # Create a bot to join the meeting + print(f"Creating bot to join meeting: {meeting_url}") + bot_data = bot.create_bot( + platform="google_meet", + meeting_url=meeting_url, + bot_name="Codegen Assistant" + ) + + bot_id = bot_data.get("id") + print(f"Bot created with ID: {bot_id}") + print(f"Bot status: {bot_data.get('status')}") + + # Wait for the bot to join the meeting + print("Waiting for bot to join the meeting...") + for _ in range(30): # Wait up to 30 seconds + bot_data = bot.get_bot(bot_id) + if bot_data.get("status") == "joined": + print("Bot has joined the meeting!") + break + time.sleep(1) + + # Stream transcription in real-time + print("Starting transcription stream...") + await bot.stream_transcription(bot_id) + + # In a real implementation, you would wait for the meeting to end + # For this example, we'll just wait a few seconds + print("Simulating meeting duration (10 seconds)...") + time.sleep(10) + + # Process the meeting recording + print("Processing meeting recording...") + meeting_data = bot.process_meeting_recording(bot_id) + print(f"Meeting data: {json.dumps(meeting_data, indent=2)}") + + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + # Run the main function + asyncio.run(main()) \ No newline at end of file diff --git a/examples/meeting_bot/zoom_sdk_poc.js b/examples/meeting_bot/zoom_sdk_poc.js new file mode 100644 index 000000000..b1473c77d --- /dev/null +++ b/examples/meeting_bot/zoom_sdk_poc.js @@ -0,0 +1,427 @@ +/** + * Codegen Meeting Bot - Zoom SDK Proof of Concept + * + * This script demonstrates how to use the Zoom Meeting SDK to create a meeting bot that can: + * 1. Join a Zoom meeting + * 2. Listen to meeting events + * 3. Interact with the meeting + * + * Requirements: + * - Node.js + * - Zoom Meeting SDK credentials + * - Puppeteer for headless browser automation + * + * Note: This is a proof-of-concept and not production-ready code. + */ + +const puppeteer = require('puppeteer'); +const express = require('express'); +const bodyParser = require('body-parser'); +const crypto = require('crypto'); +const cors = require('cors'); +const path = require('path'); +const fs = require('fs'); + +// Configuration +const PORT = process.env.PORT || 3000; +const ZOOM_SDK_KEY = process.env.ZOOM_SDK_KEY || 'your_sdk_key_here'; +const ZOOM_SDK_SECRET = process.env.ZOOM_SDK_SECRET || 'your_sdk_secret_here'; + +// Express app setup +const app = express(); +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static('public')); + +// Generate a Zoom Meeting SDK JWT token +function generateZoomToken(sdkKey, sdkSecret, meetingNumber, role) { + const iat = Math.round(new Date().getTime() / 1000) - 30; + const exp = iat + 60 * 60 * 2; // Token expires in 2 hours + const oHeader = { alg: 'HS256', typ: 'JWT' }; + + const oPayload = { + sdkKey: sdkKey, + mn: meetingNumber, + role: role, + iat: iat, + exp: exp, + tokenExp: exp + }; + + const sHeader = JSON.stringify(oHeader); + const sPayload = JSON.stringify(oPayload); + const signature = crypto.createHmac('sha256', sdkSecret) + .update(Buffer.from(sHeader + "." + sPayload).toString('base64')) + .digest('base64'); + + return Buffer.from(sHeader).toString('base64') + "." + + Buffer.from(sPayload).toString('base64') + "." + + signature; +} + +// API endpoint to generate a token +app.post('/api/token', (req, res) => { + const { meetingNumber, role } = req.body; + + if (!meetingNumber) { + return res.status(400).json({ error: 'Meeting number is required' }); + } + + const token = generateZoomToken(ZOOM_SDK_KEY, ZOOM_SDK_SECRET, meetingNumber, role || 0); + res.json({ token }); +}); + +// HTML template for the Zoom client +const zoomClientHtml = ` + + + + Codegen Zoom Bot + + + + + + + + +
+ + + + + + + + + +`; + +// Serve the Zoom client HTML +app.get('/zoom-client', (req, res) => { + res.send(zoomClientHtml); +}); + +// Start the server +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +/** + * CodegenZoomBot class for controlling a Zoom meeting bot + */ +class CodegenZoomBot { + constructor() { + this.browser = null; + this.page = null; + this.serverUrl = `http://localhost:${PORT}`; + this.isConnected = false; + this.meetingData = { + meetingNumber: '', + password: '', + userName: 'Codegen Assistant', + }; + } + + /** + * Initialize the bot + */ + async initialize() { + // Launch a headless browser + this.browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-web-security', + '--allow-running-insecure-content', + '--use-fake-ui-for-media-stream', + '--use-fake-device-for-media-stream', + ], + }); + + // Create a new page + this.page = await this.browser.newPage(); + + // Set up event listeners for messages from the Zoom client + await this.page.exposeFunction('onClientMessage', (message) => { + const { type, data } = message; + + switch (type) { + case 'CLIENT_READY': + console.log('Zoom client is ready'); + break; + case 'JOIN_SUCCESS': + console.log('Successfully joined the meeting'); + this.isConnected = true; + break; + case 'JOIN_ERROR': + console.error('Failed to join the meeting:', data); + break; + case 'CONNECTION_CHANGE': + console.log('Connection changed:', data); + break; + case 'AUDIO_CHANGE': + console.log('Audio changed:', data); + break; + case 'CHAT_RECEIVED': + console.log('Chat received:', data); + this.processChat(data); + break; + default: + console.log('Unknown message type:', type); + } + }); + + // Set up message listener + await this.page.evaluateOnNewDocument(() => { + window.addEventListener('message', (event) => { + window.onClientMessage(event.data); + }); + }); + + // Navigate to the Zoom client page + await this.page.goto(`${this.serverUrl}/zoom-client`); + + console.log('Bot initialized'); + } + + /** + * Join a Zoom meeting + * @param {string} meetingNumber - The Zoom meeting number + * @param {string} password - The Zoom meeting password + */ + async joinMeeting(meetingNumber, password) { + this.meetingData.meetingNumber = meetingNumber; + this.meetingData.password = password; + + // Get a token for the meeting + const response = await fetch(`${this.serverUrl}/api/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + meetingNumber, + role: 0, // 0 for attendee, 1 for host + }), + }); + + const { token } = await response.json(); + + // Send a message to the Zoom client to join the meeting + await this.page.evaluate((data) => { + window.postMessage({ + type: 'JOIN_MEETING', + data: { + meetingNumber: data.meetingNumber, + token: data.token, + userName: data.userName, + password: data.password, + }, + }, '*'); + }, { + meetingNumber, + token, + userName: this.meetingData.userName, + password, + }); + + console.log(`Joining meeting: ${meetingNumber}`); + } + + /** + * Leave the current Zoom meeting + */ + async leaveMeeting() { + if (!this.isConnected) { + console.log('Not connected to a meeting'); + return; + } + + // Send a message to the Zoom client to leave the meeting + await this.page.evaluate(() => { + window.postMessage({ + type: 'LEAVE_MEETING', + }, '*'); + }); + + this.isConnected = false; + console.log('Left the meeting'); + } + + /** + * Process a chat message from the meeting + * @param {Object} chatData - The chat message data + */ + async processChat(chatData) { + const { message, sender } = chatData; + + // Check if the message is directed to Codegen + if (message.toLowerCase().includes('codegen') && message.includes('?')) { + console.log(`Processing question from ${sender}: ${message}`); + + // In a real implementation, you would: + // 1. Process the question with Codegen + // 2. Generate a response + // 3. Send the response back to the chat + + // For this example, we'll just send a hardcoded response + await this.sendChatMessage(`Hello ${sender}, I'm the Codegen Assistant. I'm here to help with your Codegen questions!`); + } + } + + /** + * Send a chat message to the meeting + * @param {string} message - The message to send + */ + async sendChatMessage(message) { + if (!this.isConnected) { + console.log('Not connected to a meeting'); + return; + } + + // In a real implementation, you would use the Zoom SDK to send a chat message + // For this example, we'll just log the message + console.log(`Sending chat message: ${message}`); + } + + /** + * Close the bot + */ + async close() { + if (this.isConnected) { + await this.leaveMeeting(); + } + + if (this.browser) { + await this.browser.close(); + } + + console.log('Bot closed'); + } +} + +/** + * Example usage + */ +async function main() { + const bot = new CodegenZoomBot(); + + try { + // Initialize the bot + await bot.initialize(); + + // Join a meeting + const meetingNumber = '123456789'; + const password = 'password'; + await bot.joinMeeting(meetingNumber, password); + + // Keep the bot running for a while + console.log('Bot is running. Press Ctrl+C to exit.'); + + // Handle process termination + process.on('SIGINT', async () => { + console.log('Shutting down...'); + await bot.close(); + process.exit(0); + }); + + } catch (error) { + console.error('Error:', error); + await bot.close(); + } +} + +// Run the example if this script is executed directly +if (require.main === module) { + main(); +} + +module.exports = CodegenZoomBot; \ No newline at end of file From 148538f18ac5cd188130946d47d75179855f001c Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:42:15 +0000 Subject: [PATCH 2/2] Automated pre-commit update --- examples/meeting_bot/README.md | 28 +- examples/meeting_bot/recall_ai_poc.py | 149 +++---- examples/meeting_bot/zoom_sdk_poc.js | 562 +++++++++++++------------- 3 files changed, 373 insertions(+), 366 deletions(-) diff --git a/examples/meeting_bot/README.md b/examples/meeting_bot/README.md index b2ad216e2..ec93cdb1f 100644 --- a/examples/meeting_bot/README.md +++ b/examples/meeting_bot/README.md @@ -7,9 +7,9 @@ This directory contains proof-of-concept examples for implementing a Codegen bot The Codegen Meeting Bot is designed to: 1. Join video meetings on platforms like Google Meet and Zoom -2. Record and transcribe meetings -3. Answer questions about Codegen during meetings -4. Generate meeting summaries and action items +1. Record and transcribe meetings +1. Answer questions about Codegen during meetings +1. Generate meeting summaries and action items ## Implementation Options @@ -18,17 +18,20 @@ The Codegen Meeting Bot is designed to: This example demonstrates how to use the [Recall.ai](https://www.recall.ai/) API to create a meeting bot that works across multiple platforms. **Features:** + - Platform-agnostic implementation (works with Google Meet, Zoom, MS Teams, etc.) - Real-time transcription streaming - Meeting recording and processing - Customizable bot name and appearance **Requirements:** + - Python 3.8+ - Recall.ai API key - Required packages: `requests`, `asyncio`, `websockets` **Usage:** + ```python # Set your Recall.ai API key export RECALL_API_KEY="your_api_key_here" @@ -42,17 +45,20 @@ python recall_ai_poc.py This example demonstrates how to use the Zoom Meeting SDK to create a bot specifically for Zoom meetings. **Features:** + - Join Zoom meetings programmatically - Listen to meeting events (chat messages, participant changes, etc.) - Respond to questions in the chat - Customizable bot behavior **Requirements:** + - Node.js - Zoom Meeting SDK credentials - Required packages: `puppeteer`, `express`, `body-parser`, `crypto`, `cors` **Usage:** + ```javascript // Set your Zoom SDK credentials export ZOOM_SDK_KEY="your_sdk_key_here" @@ -70,22 +76,22 @@ node zoom_sdk_poc.js To fully integrate these examples with Codegen, you would need to: 1. Connect to the Codegen API to process questions and generate responses -2. Implement a calendar integration to automatically schedule bots for meetings -3. Create a user interface for managing bot settings and viewing meeting summaries -4. Set up secure storage for meeting recordings and transcripts +1. Implement a calendar integration to automatically schedule bots for meetings +1. Create a user interface for managing bot settings and viewing meeting summaries +1. Set up secure storage for meeting recordings and transcripts ## Next Steps These examples are proof-of-concept implementations and not production-ready. For a production implementation, consider: 1. Error handling and retry logic -2. Authentication and security -3. Scalability for multiple concurrent meetings -4. Proper logging and monitoring -5. User management and permissions +1. Authentication and security +1. Scalability for multiple concurrent meetings +1. Proper logging and monitoring +1. User management and permissions ## Resources - [Recall.ai Documentation](https://docs.recall.ai/docs/getting-started) - [Zoom Meeting SDK Documentation](https://marketplace.zoom.us/docs/sdk/native-sdks/web/) -- [Google Meet Bot Examples](https://github.com/Ritika-Das/Google-Meet-Bot) \ No newline at end of file +- [Google Meet Bot Examples](https://github.com/Ritika-Das/Google-Meet-Bot) diff --git a/examples/meeting_bot/recall_ai_poc.py b/examples/meeting_bot/recall_ai_poc.py index 901fdebba..818ca78b6 100644 --- a/examples/meeting_bot/recall_ai_poc.py +++ b/examples/meeting_bot/recall_ai_poc.py @@ -1,5 +1,4 @@ -""" -Codegen Meeting Bot - Recall.ai Proof of Concept +"""Codegen Meeting Bot - Recall.ai Proof of Concept This script demonstrates how to use Recall.ai to create a meeting bot that can: 1. Join a Google Meet or Zoom meeting @@ -15,127 +14,114 @@ Note: This is a proof-of-concept and not production-ready code. """ -import os +import asyncio import json +import os import time -import asyncio +from typing import Any, Optional + import requests -from typing import Dict, Any, Optional, List # Configuration RECALL_API_KEY = os.environ.get("RECALL_API_KEY", "your_api_key_here") RECALL_API_BASE_URL = "https://api.recall.ai/api/v1" + class CodegenMeetingBot: """A meeting bot that uses Recall.ai to join meetings and process transcripts with Codegen.""" - + def __init__(self, api_key: str): """Initialize the meeting bot with the Recall.ai API key.""" self.api_key = api_key - self.headers = { - "Authorization": f"Token {self.api_key}", - "Content-Type": "application/json" - } - - def create_bot(self, - platform: str, - meeting_url: str, - bot_name: str = "Codegen Assistant", - join_at: Optional[str] = None) -> Dict[str, Any]: - """ - Create a bot to join a meeting. - + self.headers = {"Authorization": f"Token {self.api_key}", "Content-Type": "application/json"} + + def create_bot(self, platform: str, meeting_url: str, bot_name: str = "Codegen Assistant", join_at: Optional[str] = None) -> dict[str, Any]: + """Create a bot to join a meeting. + Args: platform: The meeting platform ("zoom", "google_meet", "ms_teams", etc.) meeting_url: The URL of the meeting to join bot_name: The display name for the bot join_at: ISO 8601 timestamp for when the bot should join (None for immediate) - + Returns: The bot data from the Recall.ai API """ endpoint = f"{RECALL_API_BASE_URL}/bot/" - - payload = { - "meeting_url": meeting_url, - "platform": platform, - "bot_name": bot_name - } - + + payload = {"meeting_url": meeting_url, "platform": platform, "bot_name": bot_name} + if join_at: payload["join_at"] = join_at - + response = requests.post(endpoint, headers=self.headers, json=payload) response.raise_for_status() - + return response.json() - - def get_bot(self, bot_id: str) -> Dict[str, Any]: - """ - Get information about a bot. - + + def get_bot(self, bot_id: str) -> dict[str, Any]: + """Get information about a bot. + Args: bot_id: The ID of the bot - + Returns: The bot data from the Recall.ai API """ endpoint = f"{RECALL_API_BASE_URL}/bot/{bot_id}/" - + response = requests.get(endpoint, headers=self.headers) response.raise_for_status() - + return response.json() - - def list_bots(self, limit: int = 10) -> List[Dict[str, Any]]: - """ - List all bots. - + + def list_bots(self, limit: int = 10) -> list[dict[str, Any]]: + """List all bots. + Args: limit: Maximum number of bots to return - + Returns: List of bot data from the Recall.ai API """ endpoint = f"{RECALL_API_BASE_URL}/bot/?limit={limit}" - + response = requests.get(endpoint, headers=self.headers) response.raise_for_status() - + return response.json().get("results", []) - + async def stream_transcription(self, bot_id: str): - """ - Stream real-time transcription from a bot. - + """Stream real-time transcription from a bot. + Args: bot_id: The ID of the bot """ import websockets - + # Get the bot data to check if it's active bot = self.get_bot(bot_id) - + if bot.get("status") != "joined": print(f"Bot is not active in the meeting. Current status: {bot.get('status')}") return - + # Connect to the transcription websocket ws_url = f"wss://api.recall.ai/ws/bot/{bot_id}/transcription/" - + async with websockets.connect(ws_url, extra_headers={"Authorization": f"Token {self.api_key}"}) as websocket: print("Connected to transcription stream. Waiting for transcription...") - + while True: try: message = await websocket.recv() data = json.loads(message) - + if data.get("event") == "transcript_part": speaker = data.get("speaker", "Unknown") text = data.get("text", "") print(f"{speaker}: {text}") - + # Here you would process the transcript with Codegen # For example, detect if someone is asking a question about Codegen if "codegen" in text.lower() and "?" in text: @@ -144,51 +130,50 @@ async def stream_transcription(self, bot_id: str): # 1. Process the question with Codegen # 2. Generate a response # 3. Send the response back to the meeting (via chat or audio) - + except Exception as e: print(f"Error in transcription stream: {e}") break - - def process_meeting_recording(self, bot_id: str) -> Dict[str, Any]: - """ - Process a completed meeting recording. - + + def process_meeting_recording(self, bot_id: str) -> dict[str, Any]: + """Process a completed meeting recording. + Args: bot_id: The ID of the bot - + Returns: Processed meeting data """ # Get the bot data bot = self.get_bot(bot_id) - + if bot.get("status") not in ["left", "ended"]: print(f"Meeting is not completed yet. Current status: {bot.get('status')}") return {} - + # Get the recording URL recording_url = bot.get("video_url") - + if not recording_url: print("No recording available for this meeting.") return {} - + print(f"Recording available at: {recording_url}") - + # In a real implementation, you would: # 1. Download the recording # 2. Process the full transcript # 3. Generate a meeting summary with Codegen # 4. Extract action items # 5. Store the results - + return { "meeting_id": bot.get("id"), "duration": bot.get("duration"), "recording_url": recording_url, "platform": bot.get("platform"), "summary": "This is where the meeting summary would go.", - "action_items": ["Action item 1", "Action item 2"] + "action_items": ["Action item 1", "Action item 2"], } @@ -196,23 +181,19 @@ async def main(): """Main function to demonstrate the Codegen Meeting Bot.""" # Initialize the bot bot = CodegenMeetingBot(RECALL_API_KEY) - + # Example: Create a bot to join a Google Meet meeting meeting_url = "https://meet.google.com/abc-defg-hij" - + try: # Create a bot to join the meeting print(f"Creating bot to join meeting: {meeting_url}") - bot_data = bot.create_bot( - platform="google_meet", - meeting_url=meeting_url, - bot_name="Codegen Assistant" - ) - + bot_data = bot.create_bot(platform="google_meet", meeting_url=meeting_url, bot_name="Codegen Assistant") + bot_id = bot_data.get("id") print(f"Bot created with ID: {bot_id}") print(f"Bot status: {bot_data.get('status')}") - + # Wait for the bot to join the meeting print("Waiting for bot to join the meeting...") for _ in range(30): # Wait up to 30 seconds @@ -221,25 +202,25 @@ async def main(): print("Bot has joined the meeting!") break time.sleep(1) - + # Stream transcription in real-time print("Starting transcription stream...") await bot.stream_transcription(bot_id) - + # In a real implementation, you would wait for the meeting to end # For this example, we'll just wait a few seconds print("Simulating meeting duration (10 seconds)...") time.sleep(10) - + # Process the meeting recording print("Processing meeting recording...") meeting_data = bot.process_meeting_recording(bot_id) print(f"Meeting data: {json.dumps(meeting_data, indent=2)}") - + except Exception as e: print(f"Error: {e}") if __name__ == "__main__": # Run the main function - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/examples/meeting_bot/zoom_sdk_poc.js b/examples/meeting_bot/zoom_sdk_poc.js index b1473c77d..0c213d6ca 100644 --- a/examples/meeting_bot/zoom_sdk_poc.js +++ b/examples/meeting_bot/zoom_sdk_poc.js @@ -1,74 +1,84 @@ /** * Codegen Meeting Bot - Zoom SDK Proof of Concept - * + * * This script demonstrates how to use the Zoom Meeting SDK to create a meeting bot that can: * 1. Join a Zoom meeting * 2. Listen to meeting events * 3. Interact with the meeting - * + * * Requirements: * - Node.js * - Zoom Meeting SDK credentials * - Puppeteer for headless browser automation - * + * * Note: This is a proof-of-concept and not production-ready code. */ -const puppeteer = require('puppeteer'); -const express = require('express'); -const bodyParser = require('body-parser'); -const crypto = require('crypto'); -const cors = require('cors'); -const path = require('path'); -const fs = require('fs'); +const puppeteer = require("puppeteer"); +const express = require("express"); +const bodyParser = require("body-parser"); +const crypto = require("crypto"); +const cors = require("cors"); +const path = require("path"); +const fs = require("fs"); // Configuration const PORT = process.env.PORT || 3000; -const ZOOM_SDK_KEY = process.env.ZOOM_SDK_KEY || 'your_sdk_key_here'; -const ZOOM_SDK_SECRET = process.env.ZOOM_SDK_SECRET || 'your_sdk_secret_here'; +const ZOOM_SDK_KEY = process.env.ZOOM_SDK_KEY || "your_sdk_key_here"; +const ZOOM_SDK_SECRET = process.env.ZOOM_SDK_SECRET || "your_sdk_secret_here"; // Express app setup const app = express(); app.use(cors()); app.use(bodyParser.json()); -app.use(express.static('public')); +app.use(express.static("public")); // Generate a Zoom Meeting SDK JWT token function generateZoomToken(sdkKey, sdkSecret, meetingNumber, role) { - const iat = Math.round(new Date().getTime() / 1000) - 30; - const exp = iat + 60 * 60 * 2; // Token expires in 2 hours - const oHeader = { alg: 'HS256', typ: 'JWT' }; - - const oPayload = { - sdkKey: sdkKey, - mn: meetingNumber, - role: role, - iat: iat, - exp: exp, - tokenExp: exp - }; - - const sHeader = JSON.stringify(oHeader); - const sPayload = JSON.stringify(oPayload); - const signature = crypto.createHmac('sha256', sdkSecret) - .update(Buffer.from(sHeader + "." + sPayload).toString('base64')) - .digest('base64'); - - return Buffer.from(sHeader).toString('base64') + "." + - Buffer.from(sPayload).toString('base64') + "." + - signature; + const iat = Math.round(new Date().getTime() / 1000) - 30; + const exp = iat + 60 * 60 * 2; // Token expires in 2 hours + const oHeader = { alg: "HS256", typ: "JWT" }; + + const oPayload = { + sdkKey: sdkKey, + mn: meetingNumber, + role: role, + iat: iat, + exp: exp, + tokenExp: exp, + }; + + const sHeader = JSON.stringify(oHeader); + const sPayload = JSON.stringify(oPayload); + const signature = crypto + .createHmac("sha256", sdkSecret) + .update(Buffer.from(sHeader + "." + sPayload).toString("base64")) + .digest("base64"); + + return ( + Buffer.from(sHeader).toString("base64") + + "." + + Buffer.from(sPayload).toString("base64") + + "." + + signature + ); } // API endpoint to generate a token -app.post('/api/token', (req, res) => { - const { meetingNumber, role } = req.body; - - if (!meetingNumber) { - return res.status(400).json({ error: 'Meeting number is required' }); - } - - const token = generateZoomToken(ZOOM_SDK_KEY, ZOOM_SDK_SECRET, meetingNumber, role || 0); - res.json({ token }); +app.post("/api/token", (req, res) => { + const { meetingNumber, role } = req.body; + + if (!meetingNumber) { + return res.status(400).json({ error: "Meeting number is required" }); + } + + const token = generateZoomToken( + ZOOM_SDK_KEY, + ZOOM_SDK_SECRET, + meetingNumber, + role || 0, + ); + res.json({ token }); }); // HTML template for the Zoom client @@ -108,7 +118,7 @@ const zoomClientHtml = ` @@ -184,244 +194,254 @@ const zoomClientHtml = ` `; // Serve the Zoom client HTML -app.get('/zoom-client', (req, res) => { - res.send(zoomClientHtml); +app.get("/zoom-client", (req, res) => { + res.send(zoomClientHtml); }); // Start the server app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); + console.log(`Server running on port ${PORT}`); }); /** * CodegenZoomBot class for controlling a Zoom meeting bot */ class CodegenZoomBot { - constructor() { - this.browser = null; - this.page = null; - this.serverUrl = `http://localhost:${PORT}`; - this.isConnected = false; - this.meetingData = { - meetingNumber: '', - password: '', - userName: 'Codegen Assistant', - }; - } - - /** - * Initialize the bot - */ - async initialize() { - // Launch a headless browser - this.browser = await puppeteer.launch({ - headless: true, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-web-security', - '--allow-running-insecure-content', - '--use-fake-ui-for-media-stream', - '--use-fake-device-for-media-stream', - ], - }); - - // Create a new page - this.page = await this.browser.newPage(); - - // Set up event listeners for messages from the Zoom client - await this.page.exposeFunction('onClientMessage', (message) => { - const { type, data } = message; - - switch (type) { - case 'CLIENT_READY': - console.log('Zoom client is ready'); - break; - case 'JOIN_SUCCESS': - console.log('Successfully joined the meeting'); - this.isConnected = true; - break; - case 'JOIN_ERROR': - console.error('Failed to join the meeting:', data); - break; - case 'CONNECTION_CHANGE': - console.log('Connection changed:', data); - break; - case 'AUDIO_CHANGE': - console.log('Audio changed:', data); - break; - case 'CHAT_RECEIVED': - console.log('Chat received:', data); - this.processChat(data); - break; - default: - console.log('Unknown message type:', type); - } - }); - - // Set up message listener - await this.page.evaluateOnNewDocument(() => { - window.addEventListener('message', (event) => { - window.onClientMessage(event.data); - }); - }); - - // Navigate to the Zoom client page - await this.page.goto(`${this.serverUrl}/zoom-client`); - - console.log('Bot initialized'); - } - - /** - * Join a Zoom meeting - * @param {string} meetingNumber - The Zoom meeting number - * @param {string} password - The Zoom meeting password - */ - async joinMeeting(meetingNumber, password) { - this.meetingData.meetingNumber = meetingNumber; - this.meetingData.password = password; - - // Get a token for the meeting - const response = await fetch(`${this.serverUrl}/api/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - meetingNumber, - role: 0, // 0 for attendee, 1 for host - }), - }); - - const { token } = await response.json(); - - // Send a message to the Zoom client to join the meeting - await this.page.evaluate((data) => { - window.postMessage({ - type: 'JOIN_MEETING', - data: { - meetingNumber: data.meetingNumber, - token: data.token, - userName: data.userName, - password: data.password, - }, - }, '*'); - }, { - meetingNumber, - token, - userName: this.meetingData.userName, - password, - }); - - console.log(`Joining meeting: ${meetingNumber}`); - } - - /** - * Leave the current Zoom meeting - */ - async leaveMeeting() { - if (!this.isConnected) { - console.log('Not connected to a meeting'); - return; - } - - // Send a message to the Zoom client to leave the meeting - await this.page.evaluate(() => { - window.postMessage({ - type: 'LEAVE_MEETING', - }, '*'); - }); - - this.isConnected = false; - console.log('Left the meeting'); - } - - /** - * Process a chat message from the meeting - * @param {Object} chatData - The chat message data - */ - async processChat(chatData) { - const { message, sender } = chatData; - - // Check if the message is directed to Codegen - if (message.toLowerCase().includes('codegen') && message.includes('?')) { - console.log(`Processing question from ${sender}: ${message}`); - - // In a real implementation, you would: - // 1. Process the question with Codegen - // 2. Generate a response - // 3. Send the response back to the chat - - // For this example, we'll just send a hardcoded response - await this.sendChatMessage(`Hello ${sender}, I'm the Codegen Assistant. I'm here to help with your Codegen questions!`); - } - } - - /** - * Send a chat message to the meeting - * @param {string} message - The message to send - */ - async sendChatMessage(message) { - if (!this.isConnected) { - console.log('Not connected to a meeting'); - return; - } - - // In a real implementation, you would use the Zoom SDK to send a chat message - // For this example, we'll just log the message - console.log(`Sending chat message: ${message}`); - } - - /** - * Close the bot - */ - async close() { - if (this.isConnected) { - await this.leaveMeeting(); - } - - if (this.browser) { - await this.browser.close(); - } - - console.log('Bot closed'); - } + constructor() { + this.browser = null; + this.page = null; + this.serverUrl = `http://localhost:${PORT}`; + this.isConnected = false; + this.meetingData = { + meetingNumber: "", + password: "", + userName: "Codegen Assistant", + }; + } + + /** + * Initialize the bot + */ + async initialize() { + // Launch a headless browser + this.browser = await puppeteer.launch({ + headless: true, + args: [ + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-web-security", + "--allow-running-insecure-content", + "--use-fake-ui-for-media-stream", + "--use-fake-device-for-media-stream", + ], + }); + + // Create a new page + this.page = await this.browser.newPage(); + + // Set up event listeners for messages from the Zoom client + await this.page.exposeFunction("onClientMessage", (message) => { + const { type, data } = message; + + switch (type) { + case "CLIENT_READY": + console.log("Zoom client is ready"); + break; + case "JOIN_SUCCESS": + console.log("Successfully joined the meeting"); + this.isConnected = true; + break; + case "JOIN_ERROR": + console.error("Failed to join the meeting:", data); + break; + case "CONNECTION_CHANGE": + console.log("Connection changed:", data); + break; + case "AUDIO_CHANGE": + console.log("Audio changed:", data); + break; + case "CHAT_RECEIVED": + console.log("Chat received:", data); + this.processChat(data); + break; + default: + console.log("Unknown message type:", type); + } + }); + + // Set up message listener + await this.page.evaluateOnNewDocument(() => { + window.addEventListener("message", (event) => { + window.onClientMessage(event.data); + }); + }); + + // Navigate to the Zoom client page + await this.page.goto(`${this.serverUrl}/zoom-client`); + + console.log("Bot initialized"); + } + + /** + * Join a Zoom meeting + * @param {string} meetingNumber - The Zoom meeting number + * @param {string} password - The Zoom meeting password + */ + async joinMeeting(meetingNumber, password) { + this.meetingData.meetingNumber = meetingNumber; + this.meetingData.password = password; + + // Get a token for the meeting + const response = await fetch(`${this.serverUrl}/api/token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + meetingNumber, + role: 0, // 0 for attendee, 1 for host + }), + }); + + const { token } = await response.json(); + + // Send a message to the Zoom client to join the meeting + await this.page.evaluate( + (data) => { + window.postMessage( + { + type: "JOIN_MEETING", + data: { + meetingNumber: data.meetingNumber, + token: data.token, + userName: data.userName, + password: data.password, + }, + }, + "*", + ); + }, + { + meetingNumber, + token, + userName: this.meetingData.userName, + password, + }, + ); + + console.log(`Joining meeting: ${meetingNumber}`); + } + + /** + * Leave the current Zoom meeting + */ + async leaveMeeting() { + if (!this.isConnected) { + console.log("Not connected to a meeting"); + return; + } + + // Send a message to the Zoom client to leave the meeting + await this.page.evaluate(() => { + window.postMessage( + { + type: "LEAVE_MEETING", + }, + "*", + ); + }); + + this.isConnected = false; + console.log("Left the meeting"); + } + + /** + * Process a chat message from the meeting + * @param {Object} chatData - The chat message data + */ + async processChat(chatData) { + const { message, sender } = chatData; + + // Check if the message is directed to Codegen + if (message.toLowerCase().includes("codegen") && message.includes("?")) { + console.log(`Processing question from ${sender}: ${message}`); + + // In a real implementation, you would: + // 1. Process the question with Codegen + // 2. Generate a response + // 3. Send the response back to the chat + + // For this example, we'll just send a hardcoded response + await this.sendChatMessage( + `Hello ${sender}, I'm the Codegen Assistant. I'm here to help with your Codegen questions!`, + ); + } + } + + /** + * Send a chat message to the meeting + * @param {string} message - The message to send + */ + async sendChatMessage(message) { + if (!this.isConnected) { + console.log("Not connected to a meeting"); + return; + } + + // In a real implementation, you would use the Zoom SDK to send a chat message + // For this example, we'll just log the message + console.log(`Sending chat message: ${message}`); + } + + /** + * Close the bot + */ + async close() { + if (this.isConnected) { + await this.leaveMeeting(); + } + + if (this.browser) { + await this.browser.close(); + } + + console.log("Bot closed"); + } } /** * Example usage */ async function main() { - const bot = new CodegenZoomBot(); - - try { - // Initialize the bot - await bot.initialize(); - - // Join a meeting - const meetingNumber = '123456789'; - const password = 'password'; - await bot.joinMeeting(meetingNumber, password); - - // Keep the bot running for a while - console.log('Bot is running. Press Ctrl+C to exit.'); - - // Handle process termination - process.on('SIGINT', async () => { - console.log('Shutting down...'); - await bot.close(); - process.exit(0); - }); - - } catch (error) { - console.error('Error:', error); - await bot.close(); - } + const bot = new CodegenZoomBot(); + + try { + // Initialize the bot + await bot.initialize(); + + // Join a meeting + const meetingNumber = "123456789"; + const password = "password"; + await bot.joinMeeting(meetingNumber, password); + + // Keep the bot running for a while + console.log("Bot is running. Press Ctrl+C to exit."); + + // Handle process termination + process.on("SIGINT", async () => { + console.log("Shutting down..."); + await bot.close(); + process.exit(0); + }); + } catch (error) { + console.error("Error:", error); + await bot.close(); + } } // Run the example if this script is executed directly if (require.main === module) { - main(); + main(); } -module.exports = CodegenZoomBot; \ No newline at end of file +module.exports = CodegenZoomBot;