{
  "id": "zMqmuc3C2G5jZ6AX",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Community Contest Tracker (FB Comments) \u2192Sentiment Analysis-> Telegram Winner Alerts + Airtable Proof",
  "tags": [],
  "nodes": [
    {
      "id": "f90317ed-250a-40cd-89eb-9debba6de03d",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        -896
      ],
      "parameters": {
        "width": 532,
        "height": 662,
        "content": "# Facebook Community Contest Automator\n\n## How it works\n### This workflow automates the selection of a daily contest winner from your Facebook posts. It triggers every night at 9 PM, fetches the latest comments and cross-references them against a list of past winners from Airtable to ensure fairness. It filters out spam, uses OpenAI to identify genuinely positive comments and randomly selects a winner. Finally, it logs the winner to Airtable and announces them via Telegram (or logs errors to Supabase if something fails).\n\n**Setup Steps**\n1. **Credentials:** Configure your Facebook Graph API, Airtable, OpenAI, Telegram and Supabase credentials.\n2. **Post ID:** Update the **Get FB Comments** node with the specific `Post ID` you want to monitor.\n3. **Blocklist:** Ensure the **Get Past Winners** node points to your existing Airtable winner database.\n4. **Activate:** Switch the workflow to Active."
      },
      "typeVersion": 1
    },
    {
      "id": "63f723ea-7a08-41f2-83da-5873246b8274",
      "name": "Sticky Note - Ingestion",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 780,
        "height": 450,
        "content": "## 1. Data Ingestion\n\n**Scheduled Fetch**\nTriggers automatically every day at 21:00 (9 PM). \n\n**Context Retrieval**\nSimultaneously fetches fresh comments from the Facebook API and the list of previous winners from Airtable. This ensures the workflow has all necessary data before processing begins."
      },
      "typeVersion": 1
    },
    {
      "id": "67394214-d304-4712-811f-d29b57789595",
      "name": "Sticky Note - Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 450,
        "content": "## 2. Pre-Processing & Filter\n\n**Fairness Logic**\nExecutes custom code to create a \"Blocklist\" of users who have won in the last 30 days, ensuring prize distribution is fair.\n\n**Spam Protection**\nFilters out comments that are too short (e.g., single emojis) or match known spam patterns. If no valid users remain, the workflow halts here."
      },
      "typeVersion": 1
    },
    {
      "id": "244c77de-e58d-45dc-b4ef-5f049ef95096",
      "name": "Sticky Note - AI",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1312,
        -256
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 642,
        "content": "## 3. AI Analysis & Selection\n\n**Sentiment Engine**\nUses GPT-4o-mini to analyze the context of every eligible comment. It filters strictly for \"Positive\" sentiment to reward constructive community engagement.\n\n**Random Draw**\nFrom the pool of positive, eligible comments, the system mathematically selects one random winner."
      },
      "typeVersion": 1
    },
    {
      "id": "ffcf2f46-2ce9-490c-a3aa-c2156ea6f155",
      "name": "Sticky Note - Storage",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        -256
      ],
      "parameters": {
        "color": 7,
        "width": 850,
        "height": 620,
        "content": "## 4. Storage & Notifications\n\n**Database Sync**\nWrites the winner's details (Name, ID, Comment) into Airtable for long-term record keeping.\n\n**Multi-Channel Alerting**\nIf successful, sends a celebratory message to the Telegram channel. If the database save fails, it logs the error to Supabase and alerts the Admin immediately."
      },
      "typeVersion": 1
    },
    {
      "id": "5c7e05fc-b073-4c56-a731-2375086d9771",
      "name": "Daily Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -32,
        64
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 21 * * *"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "956ba77c-273b-4d47-b6bf-d59bc95b40f3",
      "name": "Pre-Filter (Blocklist)",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        64
      ],
      "parameters": {
        "jsCode": "// 1. Get Data from the Mock/API\nconst comments = $(\"Get FB Comments\").first().json.data || [];\nlet pastWinners = [];\ntry {\n  pastWinners = $(\"Get Past Winners\").all().map(i => i.json);\n} catch (e) {\n  pastWinners = [];\n}\n\n// 2. Create Blocklist (Users who won in last 30 days)\n// FIX: We access \"Facebook ID\" directly using brackets (because of the space)\n// We also use .filter(id => id) to skip incomplete records (like the second item in your data)\nconst winnerBlocklist = new Set(\n    pastWinners\n    .map(w => w[\"Facebook ID\"]) \n    .filter(id => id)\n);\n\n// 3. Filter Out Past Winners & Short Comments\nconst eligibleForAI = comments.filter(c => {\n  if (winnerBlocklist.has(c.from.id)) return false;\n  if (!c.message || c.message.length < 2) return false;\n  return true;\n});\n\n// 4. Return INDIVIDUAL ITEMS\nif (eligibleForAI.length === 0) {\n    return [{json: {abort: true, reason: \"No eligible new users found\"}}];\n}\n\nreturn eligibleForAI.map(c => ({\n    json: {\n        abort: false,\n        id: c.from.id,\n        name: c.from.name,\n        text: c.message\n    }\n}));"
      },
      "typeVersion": 1
    },
    {
      "id": "a5c7f7f0-128d-4ded-a7ec-2ca0a754ad07",
      "name": "Any Eligible?",
      "type": "n8n-nodes-base.if",
      "position": [
        1056,
        64
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.abort }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c8018d6e-b8fc-41fe-8047-19132c0321d0",
      "name": "Pick Random Winner",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        32
      ],
      "parameters": {
        "jsCode": "// 1. Get all analyzed items\nconst items = $input.all();\n\n// 2. Filter for POSITIVE sentiment\nconst positiveComments = items.filter(item => {\n    // FIX: Access the nested path 'sentimentAnalysis.category'\n    // Use optional chaining (?.) to prevent crashes if the field is missing\n    const s = item.json.sentimentAnalysis?.category;\n    \n    // Check if sentiment exists and is Positive\n    return s && s.toString().toLowerCase().includes('positive');\n});\n\n// 3. Validation\nif (positiveComments.length === 0) {\n   return [{json: {error: true, message: \"AI found no positive human comments\"}}];\n}\n\n// 4. Random Selection\nconst randomWinner = positiveComments[Math.floor(Math.random() * positiveComments.length)];\n\nreturn [{\n  json: {\n    error: false,\n    winner_id: randomWinner.json.id,\n    winner_name: randomWinner.json.name,\n    winner_comment: randomWinner.json.text,\n    // FIX: Map the output correctly using the same nested path\n    sentiment: randomWinner.json.sentimentAnalysis?.category\n  }\n}];"
      },
      "typeVersion": 1
    },
    {
      "id": "c1a4d98b-f55c-4143-90e3-4ffa500a0326",
      "name": "Get Past Winners",
      "type": "n8n-nodes-base.airtable",
      "position": [
        480,
        64
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appMplJTSjazXzICD",
          "cachedResultUrl": "https://airtable.com/appMplJTSjazXzICD",
          "cachedResultName": "Community Contest Tracker"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblxGqThycjphLBG9",
          "cachedResultUrl": "https://airtable.com/appMplJTSjazXzICD/tblxGqThycjphLBG9",
          "cachedResultName": "Contest Winners"
        },
        "options": {},
        "operation": "search"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "d370b18b-fd5e-49c3-880c-cfd310ad5033",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1344,
        272
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "50f3dc87-b4ac-4a1e-a0e5-5b0c7a39a320",
      "name": "Create a record",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1904,
        32
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appMplJTSjazXzICD",
          "cachedResultUrl": "https://airtable.com/appMplJTSjazXzICD",
          "cachedResultName": "Community Contest Tracker"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblxGqThycjphLBG9",
          "cachedResultUrl": "https://airtable.com/appMplJTSjazXzICD/tblxGqThycjphLBG9",
          "cachedResultName": "Contest Winners"
        },
        "columns": {
          "value": {
            "Date": "={{ $today }}",
            "Name": "={{ $json.winner_name }}",
            "Facebook ID": "={{ $json.winner_id }}"
          },
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Facebook ID",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Facebook ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "typecast": true
        },
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "bd006cda-3183-4782-a7ec-9d54a3cd5b01",
      "name": "Notify Admin (Error)1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2592,
        160
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Contest Error\n\nDetails: {{ $json.error_message || $json.message }}",
        "chatId": "@youradminchannel",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7e1ee7ea-cce9-4df2-94a8-d8aaa6fe6bb1",
      "name": "Log Error (Supabase) ",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2416,
        176
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9a277ea7-db35-46e9-879b-3c9973f9e521",
      "name": "Send a text message1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2448,
        -32
      ],
      "parameters": {
        "text": "= We have a winner! \nName:  {{ $('Pick Random Winner').item.json.winner_name }}\nCongrats on the positive vibes!",
        "chatId": "123456789",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "39010b0f-a1e4-4807-9842-0b7575bda075",
      "name": "Saved?",
      "type": "n8n-nodes-base.if",
      "position": [
        2160,
        64
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.id ? true : false }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ae99e7fe-be6c-4d33-ad7b-cdae5333c4ee",
      "name": "Sentiment Analysis",
      "type": "@n8n/n8n-nodes-langchain.sentimentAnalysis",
      "position": [
        1344,
        48
      ],
      "parameters": {
        "options": {
          "categories": "Positive, Neutral, Negative"
        },
        "inputText": "={{ $json.text }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d3d0e5ed-6575-443b-81aa-f549c79466c9",
      "name": "Get FB Comments",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        224,
        64
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v19.0/YOUR_POST_ID/comments",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "message,from"
            },
            {
              "name": "limit",
              "value": "100"
            }
          ]
        }
      },
      "typeVersion": 4.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "835829cb-4bb7-4b1e-9329-309885b7bdd9",
  "connections": {
    "Saved?": {
      "main": [
        [
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Error (Supabase) ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any Eligible?": {
      "main": [
        [],
        [
          {
            "node": "Sentiment Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Trigger": {
      "main": [
        [
          {
            "node": "Get FB Comments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a record": {
      "main": [
        [
          {
            "node": "Saved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get FB Comments": {
      "main": [
        [
          {
            "node": "Get Past Winners",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Past Winners": {
      "main": [
        [
          {
            "node": "Pre-Filter (Blocklist)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Sentiment Analysis",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Pick Random Winner": {
      "main": [
        [
          {
            "node": "Create a record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentiment Analysis": {
      "main": [
        [
          {
            "node": "Pick Random Winner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Error (Supabase) ": {
      "main": [
        [
          {
            "node": "Notify Admin (Error)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pre-Filter (Blocklist)": {
      "main": [
        [
          {
            "node": "Any Eligible?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}