Commit 7dd3b61a authored by Javad's avatar Javad

start project

parents
Pipeline #1746 failed with stages
NODE_ENV=production
PORT=3000
FB_VERIFY_TOKEN=myusedconexsecret
FB_PAGE_ACCESS_TOKEN=EAAU8nacJZALUBQFt5k4SrRuZCWefSzsRlS6v0adNdTaSdVsjkM2NMX9EMSeNTN9eh25CpWz6rWhq5ZCV2aiZBTr65MdugqCbiKiPor6LzAMlhPG6IKCY82ZBPIykoZCxsARcKmnxCSQ6tliWcN8ZA62iRQtM9C92A2GZCgVd1VzDo5t64nB9IwoAj7WSfqxpsZBl8XMHcUvyElqK8B0VaXQs4K9sf
GCLOUD_PROJECT_ID=116787824836132
GCLOUD_LOCATION=us-central1
URLSITE=https://yoursite.com
\ No newline at end of file
node_modules/
npm-debug.log
yarn.lock
.DS_Store
*.log
.env.example
# Facebook Messenger Bot with Vertex AI (Gemini)
A Node.js/Express backend that connects Facebook Page Messenger webhooks to Google Vertex AI (Gemini) for automated, intelligent responses.
## 🚀 Quick Start
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Environment Variables
Copy the example environment file:
```bash
cp .env.example .env
```
Edit `.env` and fill in the required values:
- `PORT` - Server port (default: 3000)
- `FB_VERIFY_TOKEN` - A secret token for webhook verification (create your own)
- `FB_PAGE_ACCESS_TOKEN` - Your Facebook Page Access Token (from Meta Developer Console)
- `GCLOUD_PROJECT_ID` - Your Google Cloud Project ID
- `GCLOUD_LOCATION` - Google Cloud location (default: us-central1)
### 3. Google Cloud Authentication
Set up authentication for Vertex AI:
**Option A: Service Account Key File**
- Create a service account in Google Cloud Console
- Download the JSON key file
- Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable:
```bash
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
```
**Option B: Application Default Credentials**
- If running on Google Cloud (GCE, Cloud Run, etc.), credentials are automatically detected
### 4. Run Locally
```bash
npm run dev
```
The server will start on `http://localhost:3000`
## 🌐 Exposing the Webhook Publicly
Facebook requires a publicly accessible HTTPS endpoint. For local development, use a tunneling service:
### Using ngrok
1. Install ngrok: https://ngrok.com/download
2. Expose your local server:
```bash
ngrok http 3000
```
3. Copy the HTTPS URL (e.g., `https://abc123.ngrok.io`)
## 📱 Setting Up Facebook Webhook
1. Go to [Meta for Developers](https://developers.facebook.com/)
2. Select your app and navigate to **Messenger** → **Settings**
3. Under **Webhooks**, click **Add Callback URL**
4. Enter:
- **Callback URL**: `https://your-domain.com/webhook` (or your ngrok URL)
- **Verify Token**: The same value as `FB_VERIFY_TOKEN` in your `.env`
5. Subscribe to:
- `messages`
- `messaging_postbacks`
6. Click **Verify and Save**
### Getting Page Access Token
1. In Meta Developer Console, go to **Messenger** → **Settings**
2. Under **Access Tokens**, select your Facebook Page
3. Generate a token with `pages_messaging` permission
4. Copy the token to `FB_PAGE_ACCESS_TOKEN` in your `.env`
## 🏗️ Project Structure
```
.
├── src/
│ ├── index.js # Express app entry point
│ ├── config/
│ │ └── vertex.js # Vertex AI client configuration
│ ├── services/
│ │ ├── facebook.js # Facebook Graph API service
│ │ └── handler.js # Message handler (Vertex AI → Facebook)
│ └── routes/
│ └── webhook.js # Webhook GET/POST routes
├── .env.example # Environment variables template
├── .gitignore
├── package.json
└── README.md
```
## 🔧 How It Works
1. **User sends message** → Facebook Messenger
2. **Facebook sends webhook** → POST `/webhook` on your server
3. **Handler processes** → Extracts message text and sender ID
4. **Vertex AI generates response** → Using Gemini model
5. **Response sent back** → Via Facebook Graph API to user
## 📝 API Endpoints
- `GET /` - Health check endpoint
- `GET /webhook` - Webhook verification (Facebook calls this)
- `POST /webhook` - Receives incoming messages from Facebook
## 🚢 Deployment
This backend can be deployed on:
- **VPS** (Ubuntu, etc.) - Use PM2 or systemd
- **Heroku** - Set environment variables in dashboard
- **Google Cloud Run** - Containerized deployment
- **Railway/Render** - Simple PaaS deployment
Make sure to:
- Set all environment variables in your hosting platform
- Ensure `GOOGLE_APPLICATION_CREDENTIALS` is set (or use Application Default Credentials)
- Update Facebook webhook URL to your production domain
- Use HTTPS (required by Facebook)
## 🔒 Security Notes
- Never commit `.env` file
- Keep `FB_VERIFY_TOKEN` secret
- Rotate `FB_PAGE_ACCESS_TOKEN` periodically
- Use environment variables for all sensitive data
## 📚 Dependencies
- **express** - Web framework
- **axios** - HTTP client for Facebook Graph API
- **dotenv** - Environment variable management
- **@google-cloud/vertexai** - Google Vertex AI SDK
## 🐛 Troubleshooting
- **Webhook verification fails**: Check that `FB_VERIFY_TOKEN` matches exactly
- **Messages not received**: Verify webhook subscription includes `messages` event
- **Vertex AI errors**: Check Google Cloud credentials and project permissions
- **Facebook API errors**: Verify `FB_PAGE_ACCESS_TOKEN` is valid and has `pages_messaging` permission
## 📄 License
ISC
const express = require("express");
const webhookRouter = require("./src/routes/webhook");
const testRouter = require("./src/routes/test");
require("dotenv").config();
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get("/", (req, res) => {
res.json({
status: "ok",
message: "fb-vertex-bot running",
endpoints: {
health: "GET /",
webhook: "GET/POST /webhook",
test: "GET /test",
},
});
});
app.use("/webhook", webhookRouter);
app.use("/test", testRouter);
// برای Fly.io: از environment variables استفاده کنید
const PORT = process.env.PORT || 3000;
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
});
\ No newline at end of file
{
"name": "fb-vertex-bot",
"version": "1.0.0",
"description": "Facebook Messenger bot powered by Vertex AI (Gemini)",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"build": "npm install --production && npm run build:copy",
"build:copy": "node scripts/build.js",
"build:full": "npm install",
"production": "NODE_ENV=production node index.js"
},
"keywords": [
"facebook",
"messenger",
"vertex-ai",
"gemini",
"bot"
],
"author": "",
"license": "ISC",
"dependencies": {
"@google-cloud/vertexai": "^1.0.0",
"axios": "^1.6.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"flyctl": "^1.2.6"
},
"devDependencies": {
"nodemon": "^3.1.11"
}
}
{
"PORT": 3000,
"FB_VERIFY_TOKEN": "myusedconexsecret",
"FB_PAGE_ACCESS_TOKEN": "EAAU8nacJZALUBQFt5k4SrRuZCWefSzsRlS6v0adNdTaSdVsjkM2NMX9EMSeNTN9eh25CpWz6rWhq5ZCV2aiZBTr65MdugqCbiKiPor6LzAMlhPG6IKCY82ZBPIykoZCxsARcKmnxCSQ6tliWcN8ZA62iRQtM9C92A2GZCgVd1VzDo5t64nB9IwoAj7WSfqxpsZBl8XMHcUvyElqK8B0VaXQs4K9sf",
"GCLOUD_PROJECT_ID": "116787824836132",
"GCLOUD_LOCATION": "us-central1",
"URLSITE": "https://yoursite.com"
}
\ No newline at end of file
const { VertexAI } = require("@google-cloud/vertexai");
const config = require("./config.json");
// Initialize Vertex AI client
const projectId = config.GCLOUD_PROJECT_ID;
const location = config.GCLOUD_LOCATION || "us-central1";
if (!projectId) {
throw new Error("GCLOUD_PROJECT_ID environment variable is required");
}
// Create Vertex AI instance
const vertexAI = new VertexAI({ project: projectId, location: location });
// Initialize the generative model
const model = vertexAI.getGenerativeModel({
model: "gemini-1.5-flash",
});
/**
* Sends a prompt to Vertex AI (Gemini) and returns the generated response
* @param {string} prompt - The prompt to send to the model
* @returns {Promise<string>} - The generated text response
* @throws {Error} - If Vertex AI API fails
*/
async function askVertex(prompt) {
try {
const request = {
contents: [{ role: "user", parts: [{ text: prompt }] }],
};
const result = await model.generateContent(request);
const response = result.response;
if (response.candidates && response.candidates.length > 0) {
const candidate = response.candidates[0];
if (
candidate.content &&
candidate.content.parts &&
candidate.content.parts.length > 0
) {
const text = candidate.content.parts[0].text;
return text ? text.trim() : "";
}
}
return "";
} catch (error) {
console.error("VertexAI error:", error.message);
console.error("Full error:", error);
throw new Error("VertexAI error: " + error.message);
}
}
module.exports = { askVertex };
const express = require("express");
const router = express.Router();
const { askVertex } = require("../config/vertex");
const { sendMessageToUser } = require("../services/facebook");
const { handleIncomingMessage } = require("../services/handler");
const config = require("../config/config.json");
/**
* GET /test - Test all components
* Returns status of all services
*/
router.get("/", (req, res) => {
console.log("Testing environment variables...");
const envCheck = {
FB_PAGE_ACCESS_TOKEN: config.FB_PAGE_ACCESS_TOKEN ? "✅ Set" : "❌ Missing",
FB_VERIFY_TOKEN: config.FB_VERIFY_TOKEN ? "✅ Set" : "❌ Missing",
GCLOUD_PROJECT_ID: config.GCLOUD_PROJECT_ID ? "✅ Set" : "❌ Missing",
GCLOUD_LOCATION: config.GCLOUD_LOCATION || "us-central1 (default)",
};
res.json({
status: "ok",
message: "Test endpoint",
environment: envCheck,
endpoints: {
testVertex: "POST /test/vertex - Test Vertex AI",
testFacebook: "POST /test/facebook - Test Facebook API",
testHandler: "POST /test/handler - Test complete handler",
testAll: "POST /test/all - Test everything",
},
});
});
/**
* POST /test/vertex - Test Vertex AI connection
* Body: { prompt: "your test prompt" }
*/
router.post("/vertex", async (req, res) => {
try {
const { prompt } = req.body;
const testPrompt = prompt || "سلام، چطوری؟";
console.log("Testing Vertex AI with prompt:", testPrompt);
const response = await askVertex(testPrompt);
res.json({
status: "success",
message: "Vertex AI test successful",
prompt: testPrompt,
response: response,
});
} catch (error) {
console.error("Vertex AI test failed:", error);
res.status(500).json({
status: "error",
message: "Vertex AI test failed",
error: error.message,
});
}
});
/**
* POST /test/facebook - Test Facebook API
* Body: { recipientId: "user_id", message: "test message" }
*/
router.post("/facebook", async (req, res) => {
try {
const { recipientId, message } = req.body;
if (!recipientId) {
return res.status(400).json({
status: "error",
message: "recipientId is required",
});
}
const testMessage = message || "این یک پیام تست است 🧪";
console.log(`Testing Facebook API - Sending to ${recipientId}:`, testMessage);
await sendMessageToUser(recipientId, testMessage);
res.json({
status: "success",
message: "Facebook API test successful",
recipientId: recipientId,
sentMessage: testMessage,
});
} catch (error) {
console.error("Facebook API test failed:", error);
res.status(500).json({
status: "error",
message: "Facebook API test failed",
error: error.message,
details: error.response?.data || null,
});
}
});
/**
* POST /test/handler - Test complete handler (Vertex AI + Facebook)
* Body: { senderId: "user_id", userText: "test message" }
*/
router.post("/handler", async (req, res) => {
try {
const { senderId, userText } = req.body;
if (!senderId) {
return res.status(400).json({
status: "error",
message: "senderId is required",
});
}
const testText = userText || "سلام، تست می‌کنم";
console.log(`Testing handler - From ${senderId}:`, testText);
// Call handler (it will send message automatically)
await handleIncomingMessage(senderId, testText);
res.json({
status: "success",
message: "Handler test successful - Message sent to user",
senderId: senderId,
userText: testText,
note: "Check Facebook Messenger for the response",
});
} catch (error) {
console.error("Handler test failed:", error);
res.status(500).json({
status: "error",
message: "Handler test failed",
error: error.message,
});
}
});
/**
* POST /test/all - Test everything in sequence
* Body: { senderId: "user_id", prompt: "optional custom prompt" }
*/
router.post("/all", async (req, res) => {
const results = {
vertex: null,
facebook: null,
handler: null,
};
const { senderId, prompt } = req.body;
const testPrompt = prompt || "سلام، این یک تست کامل است";
const testSenderId = senderId || config.GCLOUD_PROJECT_ID;
if (!testSenderId) {
return res.status(400).json({
status: "error",
message: "senderId is required (or set GCLOUD_PROJECT_ID in env)",
});
}
// Test 1: Vertex AI
try {
console.log("Testing Vertex AI...");
const vertexResponse = await askVertex(testPrompt);
results.vertex = {
status: "success",
response: vertexResponse.substring(0, 100) + "...",
};
} catch (error) {
results.vertex = {
status: "error",
error: error.message,
};
}
// Test 2: Facebook API
try {
console.log("Testing Facebook API...");
await sendMessageToUser(testSenderId, "🧪 تست Facebook API");
results.facebook = {
status: "success",
message: "Test message sent",
};
} catch (error) {
results.facebook = {
status: "error",
error: error.message,
};
}
// Test 3: Complete Handler
try {
console.log("Testing complete handler...");
await handleIncomingMessage(testSenderId, testPrompt);
results.handler = {
status: "success",
message: "Handler executed successfully",
};
} catch (error) {
results.handler = {
status: "error",
error: error.message,
};
}
const allSuccess =
results.vertex.status === "success" &&
results.facebook.status === "success" &&
results.handler.status === "success";
res.json({
status: allSuccess ? "success" : "partial",
message: allSuccess
? "All tests passed!"
: "Some tests failed. Check details below.",
results: results,
});
});
module.exports = router;
const express = require('express');
const router = express.Router();
const { handleIncomingMessage } = require('../services/handler');
const config = require("../config/config.json");
const FB_VERIFY_TOKEN = config.FB_VERIFY_TOKEN;
/**
* GET /webhook - Webhook verification for Facebook
* Facebook will call this to verify the webhook endpoint
*/
router.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === FB_VERIFY_TOKEN) {
console.log('WEBHOOK_VERIFIED');
res.status(200).send(challenge);
} else {
console.error('Webhook verification failed');
res.sendStatus(403);
}
});
/**
* POST /webhook - Receives incoming messages from Facebook Messenger
* Processes messages and triggers the handler
*/
router.post('/webhook', async (req, res) => {
try {
const body = req.body;
// Check if this is a page event
if (body.object === 'page') {
// Process each entry
for (const entry of body.entry) {
const messaging = entry.messaging;
if (messaging && Array.isArray(messaging)) {
for (const event of messaging) {
const senderId = event.sender?.id;
// Check if this is a text message
if (event.message && event.message.text && senderId) {
const userText = event.message.text;
console.log(`Received message from ${senderId}: ${userText}`);
// Handle the message asynchronously (don't wait for it)
handleIncomingMessage(senderId, userText).catch((error) => {
console.error('Error handling message:', error);
});
}
}
}
}
// Return 200 immediately to acknowledge receipt
res.status(200).send('EVENT_RECEIVED');
} else {
// Not a page event
res.sendStatus(404);
}
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).send('Internal Server Error');
}
});
module.exports = router;
const axios = require('axios');
const config = require("../config/config.json");
const FB_PAGE_ACCESS_TOKEN = config.FB_PAGE_ACCESS_TOKEN;
if (!FB_PAGE_ACCESS_TOKEN) {
console.warn('Warning: FB_PAGE_ACCESS_TOKEN is not set');
}
/**
* Sends a text message to a Facebook user via Graph API
* @param {string} recipientId - The Facebook user ID (sender ID)
* @param {string} text - The message text to send
* @returns {Promise<void>}
*/
async function sendMessageToUser(recipientId, text) {
if (!FB_PAGE_ACCESS_TOKEN) {
throw new Error('FB_PAGE_ACCESS_TOKEN is not configured');
}
const url = `https://graph.facebook.com/v18.0/me/messages?access_token=${FB_PAGE_ACCESS_TOKEN}`;
const payload = {
recipient: { id: recipientId },
message: { text: text },
};
try {
const response = await axios.post(url, payload, {
headers: {
'Content-Type': 'application/json',
},
});
console.log('Message sent successfully:', response.data);
} catch (error) {
console.error('Error sending message to Facebook:');
console.error('Status:', error.response?.status);
console.error('Data:', error.response?.data);
console.error('Message:', error.message);
throw error;
}
}
module.exports = { sendMessageToUser };
const { askVertex } = require('../config/vertex');
const { sendMessageToUser } = require('./facebook');
/**
* System prompt for the AI assistant
*/
const systemPrompt = `You are a helpful, polite assistant acting as the official agent of a Facebook Page.
- Answer briefly and clearly.
- Answer in the same language as the user (if user writes in Persian, answer in Persian).
- Do NOT say you are an AI, just act as the page's assistant.
- If you are not sure about pricing, delivery, or internal policies, ask a follow-up question or say you will check.`;
/**
* Handles an incoming message from a Facebook user
* Processes the message through Vertex AI and sends a response back
* @param {string} senderId - The Facebook user ID (sender ID)
* @param {string} userText - The message text from the user
* @returns {Promise<void>}
*/
async function handleIncomingMessage(senderId, userText) {
try {
// Build the full prompt
const fullPrompt = systemPrompt + '\n\nUser: ' + userText;
// Get response from Vertex AI
let reply = await askVertex(fullPrompt);
// Fallback if reply is empty or too short
if (!reply || reply.trim().length < 3) {
reply = 'متاسفم، الان نتونستم خوب پاسخ بدم. لطفاً دوباره سؤال‌تان را بپرسید.';
}
// Send the reply back to the user
await sendMessageToUser(senderId, reply);
} catch (error) {
console.error('Error in handleIncomingMessage:', error.message);
console.error('Full error:', error);
// Send fallback error message to user
try {
await sendMessageToUser(
senderId,
'متاسفم، یک خطای فنی رخ داد. لطفاً چند دقیقه بعد دوباره پیام بدهید.'
);
} catch (sendError) {
console.error('Failed to send error message to user:', sendError.message);
}
}
}
module.exports = { handleIncomingMessage };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment