commit 73e7d4cd66408e09d57f816e41c44f871571fa8b Author: jorge.alves Date: Thu Aug 20 13:49:31 2026 +0000 Upload files to "/" first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..47ca85c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM node:20-alpine + +# Install Nginx to serve static frontend +RUN apk add --no-cache nginx + +WORKDIR /app + +# Copy application files +COPY index.html /usr/share/nginx/html/index.html +COPY generate-meet.js /app/generate-meet.js + +# Configure Nginx default site to serve index.html on port 80 +RUN echo 'server { \ + listen 80; \ + location / { \ + root /usr/share/nginx/html; \ + index index.html; \ + } \ +}' > /etc/nginx/http.d/default.conf + +# Expose HTTP frontend port (80) and Backend API port (8585) +EXPOSE 80 8585 + +# Launch Nginx in background and run Node backend on port 8585 +CMD ["sh", "-c", "nginx && node /app/generate-meet.js 8585"] \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..5e4a9a4 --- /dev/null +++ b/README.txt @@ -0,0 +1,7 @@ +# 1. Build the Docker image +docker build -t meet-generator . + +# 2. Run the container mapping frontend (9595) and backend (8585) ports +docker run -d --name google-meet-app -v "$(pwd)/index.html:/usr/share/nginx/html/index.html" -p 9595:80 -p 8585:8585 meet-generator + +# 3. Access the public url http://localhost:9595/ \ No newline at end of file diff --git a/generate-meet.js b/generate-meet.js new file mode 100644 index 0000000..8b3a217 --- /dev/null +++ b/generate-meet.js @@ -0,0 +1,112 @@ +const http = require('http'); +// generate-meet.js + +// 1. CONFIGURATION - Paste your keys here +const CLIENT_ID = '977481853112-pdqmfvqcgnnuqbrbi88r84mgv14mqbq8.apps.googleusercontent.com'; +const CLIENT_SECRET = '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'; +const REFRESH_TOKEN = '1//04hBu9T1sojSVCgYIARAAGAQSNwF-L9IrXYktOSabjSSDtaBVKW3xRaqYKhp3x7brpaMAvcBK5rNNj2cQQMTuk0_HTkQWnJ2maLo'; + +const PORT = process.argv[2] || process.env.PORT || 3000; + +// access token: ya29.a0AT3oNZ_5YKJidHRWMmuU7hvgcbXyNH6xttPtx2eMj0pBXPbeElKdOVOBEblX9-oVAcIjBxPlZF3yfm56lakEfu4guLDxGX4oPUxUnArEje425vee0ZvyV9ZObIvCrVhht5wD9lsR7cmg78JUE10dvfY2J9ebWZluwEMSChtIY4tk5FdyGa8sKqXyD0yTRheuYIOkR5kaCgYKAb4SARQSFQHGX2MiT1ih_xx7ZmodLXopYQ3zsw0206 +/** + * Step 1: Silently exchange the permanent Refresh Token for a temporary 1-hour Access Token + */ +async function getAccessToken() { + const response = await fetch('https://oauth2.googleapis.com/token', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + refresh_token: REFRESH_TOKEN, + grant_type: 'refresh_token' + }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(`Token Refresh Failed: ${data.error_description || data.error}`); + } + return data.access_token; +} + +/** + * Step 2: Use the Access Token to create an OPEN meeting space (no knocking required) + */ +async function createOpenMeetSpace(accessToken) { + const response = await fetch('https://meet.googleapis.com/v2/spaces', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + // "accessType: OPEN" makes sure anyone clicking this link enters instantly without waiting in a lobby + body: JSON.stringify({ + config: { + accessType: "OPEN" + } + }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(`Meet API Error: ${data.error.message}`); + } + return data.meetingUri; +} + +// 3. HTTP SERVER SETUP +const server = http.createServer(async (req, res) => { + // Enable CORS headers so your frontend script can call this server + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + // Handle browser CORS preflight check + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + // Server endpoint matching /webhook/new-meet + if (req.url === '/webhook/new-meet' || req.url === '/generate-meet') { + try { + console.log(`[${new Date().toISOString()}] 🔄 Connecting to Google Cloud...`); + const accessToken = await getAccessToken(); + + console.log(`[${new Date().toISOString()}] 🚀 Creating open Google Meet room...`); + const meetLink = await createOpenMeetSpace(accessToken); + + console.log(`[${new Date().toISOString()}] 🎉 Link generated: ${meetLink}`); + + // Return JSON payload to the frontend + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + meetLink: meetLink + })); + + } catch (error) { + console.error(`[${new Date().toISOString()}] ❌ Error:`, error.message); + + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: false, + error: error.message + })); + } + } else { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Endpoint not found' })); + } +}); + +// Start listening +server.listen(PORT, () => { + console.log(`=========================================`); + console.log(`🚀 Server running at http://localhost:${PORT}`); + console.log(`👉 Endpoint: http://localhost:${PORT}/webhook/new-meet`); + console.log(`=========================================`); +}); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..bffd2e6 --- /dev/null +++ b/index.html @@ -0,0 +1,211 @@ + + + + + + Google Meet Generator + + + + + + + + + + \ No newline at end of file