Skip to content

[TRIGGERS] Facebook Pages #17981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions components/facebook_pages/.gitignore

This file was deleted.

13 changes: 0 additions & 13 deletions components/facebook_pages/app/facebook_pages.app.ts

This file was deleted.

49 changes: 49 additions & 0 deletions components/facebook_pages/facebook_pages.app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ export default {
description: "The maximum number of results to return",
optional: true,
},
appId: {
type: "string",
label: "App ID",
description: "The Facebook App ID. You can find this in your Facebook App Dashboard.",
},
},
methods: {
_baseUrl() {
Expand Down Expand Up @@ -139,6 +144,12 @@ export default {
const page = data.find(({ id }) => id == pageId);
return page.access_token;
},
getAppAccessToken(args = {}) {
return this._makeRequest({
path: "/oauth/access_token",
...args,
});
},
getPost({
pageId, postId, ...args
}) {
Expand Down Expand Up @@ -221,5 +232,43 @@ export default {
...args,
});
},
createSubscription({
appId, ...args
} = {}) {
return this._makeRequest({
path: `/${appId}/subscriptions`,
method: "POST",
...args,
});
},
deleteSubscription({
appId, ...args
} = {}) {
return this._makeRequest({
path: `/${appId}/subscriptions`,
method: "DELETE",
...args,
});
},
createPageSubscription({
pageId, ...args
} = {}) {
return this._makeRequest({
path: `/${pageId}/subscribed_apps`,
method: "POST",
pageId,
...args,
});
},
deletePageSubscription({
pageId, ...args
} = {}) {
return this._makeRequest({
path: `/${pageId}/subscribed_apps`,
method: "DELETE",
pageId,
...args,
});
},
},
};
4 changes: 2 additions & 2 deletions components/facebook_pages/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/facebook_pages",
"version": "0.1.0",
"version": "0.2.0",
"description": "Pipedream Facebook Pages Components",
"main": "facebook_pages.app.mjs",
"keywords": [
Expand All @@ -13,6 +13,6 @@
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^1.5.1"
"@pipedream/platform": "^3.1.0"
}
}
80 changes: 80 additions & 0 deletions components/facebook_pages/sources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Facebook Pages Webhook Sources

This directory contains webhook sources for Facebook Pages events. These sources allow you to receive real-time notifications when various events occur on your Facebook Page.

## Available Sources

### 1. New Feed Activity (`new-feed-activity`)
Emit new event when there's any new activity in your Facebook Page's feed. This includes new posts, comments, reactions, and shares.

### 2. New Post to Page (`new-post`)
Emit new event when a new post is made to your Facebook Page's feed.

### 3. New Comment on Post (`new-comment`)
Emit new event when a new comment is added to a post on your Facebook Page.

### 4. New Reaction on Post (`new-reaction`)
Emit new event when someone reacts to a post on your Facebook Page (likes, love, wow, etc.).

### 5. New Share of Post (`new-share`)
Emit new event when someone shares a post from your Facebook Page.

### 6. Page Updated (`page-updated`)
Emit new event when your Facebook Page information is updated (such as description, hours, location, etc.).

### 7. New Message Received (`new-message`)
Emit new event when your Facebook Page receives a new message via Messenger.

## Setup Instructions

To use these webhook sources, you need to:

1. **Create a Facebook App**:
- Go to [Facebook Developers](https://developers.facebook.com/)
- Create a new app or use an existing one
- Note your App ID

2. **Configure Webhooks in Facebook**:
- In your Facebook App dashboard, go to Webhooks
- Subscribe to the "Page" object
- When deploying a source in Pipedream, you'll get a webhook URL and verify token
- Use these values in your Facebook webhook configuration

3. **Set Required Permissions**:
- For feed webhooks: `pages_manage_metadata` and `pages_show_list`
- For message webhooks: `pages_messaging`

4. **Subscribe Your Page**:
- The webhook source will automatically subscribe your selected page to receive events
- Make sure your page has not disabled the App platform in its settings

## Common Properties

All webhook sources share these properties:

- **Facebook Pages App**: Your Facebook Pages connection
- **Page**: The Facebook Page to monitor for events
- **Verify Token**: A custom string for webhook verification (auto-generated but can be customized)

## Event Data

Each webhook event includes relevant data such as:
- Event type and timestamp
- User information (when available)
- Content (messages, post text, etc.)
- Related IDs (post_id, comment_id, etc.)

## Troubleshooting

1. **Webhook not receiving events**:
- Ensure your Facebook App has the correct permissions
- Verify the webhook is properly configured in Facebook
- Check that your page has the app installed

2. **Verification failing**:
- Make sure the verify token matches exactly between Pipedream and Facebook
- The webhook URL must be publicly accessible (HTTPS)

3. **Missing events**:
- Some events require specific permissions
- Check Facebook's webhook documentation for limitations
112 changes: 112 additions & 0 deletions components/facebook_pages/sources/common/webhook.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import crypto from "crypto";
import app from "../../facebook_pages.app.mjs";

export default {
props: {
app,
db: "$.service.db",
http: {
type: "$.interface.http",
customResponse: true,
},
page: {
propDefinition: [
app,
"page",
],
},
verifyToken: {
type: "string",
label: "Verify Token",
description: "A custom string you provide to Facebook for webhook verification. Facebook will send this token back when verifying your webhook endpoint.",
default: crypto.randomBytes(16).toString("hex"),
},
},
hooks: {
async activate() {
// Subscribe the page to the app with specified fields (page-level webhooks)
await this.app.createPageSubscription({
pageId: this.page,
params: {
subscribed_fields: this.getFields().join(","),
},
});

// Store page ID for deactivation
this._setPageId(this.page);

console.log(`Webhook URL: ${this.http.endpoint}`);
console.log(`Verify Token: ${this.verifyToken}`);
console.log("Subscribed page-level webhooks for fields:", this.getFields());
},
Comment on lines +26 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for subscription creation.

The activation hook should handle potential failures when creating the page subscription to prevent storing invalid state.

 async activate() {
-  // Subscribe the page to the app with specified fields (page-level webhooks)
-  await this.app.createPageSubscription({
-    pageId: this.page,
-    params: {
-      subscribed_fields: this.getFields().join(","),
-    },
-  });
-
-  // Store page ID for deactivation
-  this._setPageId(this.page);
+  try {
+    // Subscribe the page to the app with specified fields (page-level webhooks)
+    await this.app.createPageSubscription({
+      pageId: this.page,
+      params: {
+        subscribed_fields: this.getFields().join(","),
+      },
+    });
+
+    // Store page ID for deactivation
+    this._setPageId(this.page);
+  } catch (error) {
+    console.error("Failed to create page subscription:", error);
+    throw error;
+  }

   console.log(`Webhook URL: ${this.http.endpoint}`);
   console.log(`Verify Token: ${this.verifyToken}`);
   console.log("Subscribed page-level webhooks for fields:", this.getFields());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async activate() {
// Subscribe the page to the app with specified fields (page-level webhooks)
await this.app.createPageSubscription({
pageId: this.page,
params: {
subscribed_fields: this.getFields().join(","),
},
});
// Store page ID for deactivation
this._setPageId(this.page);
console.log(`Webhook URL: ${this.http.endpoint}`);
console.log(`Verify Token: ${this.verifyToken}`);
console.log("Subscribed page-level webhooks for fields:", this.getFields());
},
async activate() {
try {
// Subscribe the page to the app with specified fields (page-level webhooks)
await this.app.createPageSubscription({
pageId: this.page,
params: {
subscribed_fields: this.getFields().join(","),
},
});
// Store page ID for deactivation
this._setPageId(this.page);
} catch (error) {
console.error("Failed to create page subscription:", error);
throw error;
}
console.log(`Webhook URL: ${this.http.endpoint}`);
console.log(`Verify Token: ${this.verifyToken}`);
console.log("Subscribed page-level webhooks for fields:", this.getFields());
},
🤖 Prompt for AI Agents
In components/facebook_pages/sources/common/webhook.mjs around lines 26 to 41,
the activate method lacks error handling for the asynchronous
createPageSubscription call. To fix this, wrap the subscription creation in a
try-catch block, and only call _setPageId and log success messages if the
subscription succeeds. In the catch block, log or handle the error appropriately
to prevent storing invalid state when subscription creation fails.

async deactivate() {
const pageId = this._getPageId();
if (pageId) {
await this.app.deletePageSubscription({
pageId,
});
}
},
},
methods: {
_getPageId() {
return this.db.get("pageId");
},
_setPageId(pageId) {
this.db.set("pageId", pageId);
},
getFields() {
throw new Error("getFields is not implemented");
},
generateMeta() {
throw new Error("generateMeta is not implemented");
},
processEvent() {
throw new Error("processEvent is not implemented");
},
},
async run({
query, body, method,
}) {
// Handle webhook verification from Facebook
if (method === "GET") {
const mode = query["hub.mode"];
const token = query["hub.verify_token"];
const challenge = query["hub.challenge"];

if (mode === "subscribe" && token === this.verifyToken) {
console.log("Webhook verified");
this.http.respond({
status: 200,
body: challenge,
});
return;
} else {
console.log("Webhook verification failed");
this.http.respond({
status: 403,
});
return;
}
}

// Handle webhook events
this.http.respond({
status: 200,
});

if (body?.object === "page" && body?.entry) {
body.entry.forEach((entry) => {
if (entry.changes) {
entry.changes.forEach((change) => {
const eventData = this.processEvent(change);
if (eventData) {
const meta = this.generateMeta(eventData);
this.$emit(eventData, meta);
}
});
}
});
}
},
};
Comment on lines +93 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling in event processing loop.

The event processing should handle errors gracefully to prevent one malformed event from affecting others.

 // Handle webhook events
 this.http.respond({
   status: 200,
 });

 if (body?.object === "page" && body?.entry) {
   body.entry.forEach((entry) => {
     if (entry.changes) {
       entry.changes.forEach((change) => {
-        const eventData = this.processEvent(change);
-        if (eventData) {
-          const meta = this.generateMeta(eventData);
-          this.$emit(eventData, meta);
-        }
+        try {
+          const eventData = this.processEvent(change);
+          if (eventData) {
+            const meta = this.generateMeta(eventData);
+            this.$emit(eventData, meta);
+          }
+        } catch (error) {
+          console.error("Error processing event:", error, change);
+        }
       });
     }
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle webhook events
this.http.respond({
status: 200,
});
if (body?.object === "page" && body?.entry) {
body.entry.forEach((entry) => {
if (entry.changes) {
entry.changes.forEach((change) => {
const eventData = this.processEvent(change);
if (eventData) {
const meta = this.generateMeta(eventData);
this.$emit(eventData, meta);
}
});
}
});
}
},
};
// Handle webhook events
this.http.respond({
status: 200,
});
if (body?.object === "page" && body?.entry) {
body.entry.forEach((entry) => {
if (entry.changes) {
entry.changes.forEach((change) => {
try {
const eventData = this.processEvent(change);
if (eventData) {
const meta = this.generateMeta(eventData);
this.$emit(eventData, meta);
}
} catch (error) {
console.error("Error processing event:", error, change);
}
});
}
});
}
🤖 Prompt for AI Agents
In components/facebook_pages/sources/common/webhook.mjs around lines 93 to 112,
the event processing loop lacks error handling, which means a single malformed
event could disrupt processing of others. Wrap the processing logic inside the
forEach loops with try-catch blocks to catch and log errors without stopping the
iteration, ensuring all events are processed independently and errors are
handled gracefully.

57 changes: 57 additions & 0 deletions components/facebook_pages/sources/new-comment/new-comment.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import common from "../common/webhook.mjs";

export default {
...common,
key: "facebook_pages-new-comment",
name: "New Comment on Post",
description: "Emit new event when a new comment is added to a post on your Facebook Page.",
version: "0.0.1",
type: "source",
dedupe: "unique",
methods: {
...common.methods,
getFields() {
return [
"feed",
];
},
generateMeta(data) {
const {
comment_id, parent_id, created_time, from, message,
} = data;
const ts = created_time
? created_time * 1000
: Date.now();
const id = comment_id || `comment-${parent_id}-${ts}`;

let summary = "New comment";
if (from?.name) {
summary = `New comment by ${from.name}`;
}
if (message) {
const preview = message.substring(0, 50);
summary += `: ${preview}${message.length > 50
? "..."
: ""}`;
}

return {
id,
summary,
ts,
};
},
processEvent(change) {
if (change.field === "feed" && change.value) {
const {
item, verb,
} = change.value;
// Only emit events for new comments
if (item === "comment" && verb === "add") {
return change.value;
}
}
return null;
},
},
};
Loading
Loading