{
  "id": "iWO9TnNMXtSwdZ3Y",
  "name": "AI-Driven Discord Moderation with Google Sheets Learning & Admin Insights",
  "tags": [],
  "nodes": [
    {
      "id": "44a9a680-a894-4d11-9560-805023a4316c",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        240,
        240
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "aec37f9f-3869-49b3-9d41-5b8b429c1f84",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1328,
        176
      ],
      "parameters": {
        "text": "=LEARN FROM THESE EXAMPLES:\n\nMessages to ALWAYS DELETE:\n- {{ $json.badExamples }}\n\nMessages to NEVER DELETE:\n- {{ $json.goodExamples }}\n\nNow analyze these messages:\n{{ $json.messageList }}",
        "options": {
          "systemMessage": "=You are a content moderator for 'The Daily Hustle' - a forex trading academy Discord community. Your job is to identify and flag messages that violate community standards.\n\nIMPORTANT: Focus on INTENT and CONTEXT, not just language. Profanity in positive/supportive messages is ALLOWED.\n\nDELETE messages that contain:\n- Personal attacks, insults, or harassment toward members/community (e.g., \"you guys are shit\", \"this firm is bad\")\n- Degrading comments about the academy, mentors, or trading strategies\n- Spam, scams, or fraudulent trading schemes\n- Toxic negativity that damages community morale\n- Hate speech, discrimination, or offensive content\n- Messages that undermine the academy's credibility or reputation\n- Threats or aggressive behavior\n\nKEEP messages that:\n- Express positive emotions even with profanity (e.g., \"I fucking love you guys\", \"this is fucking amazing\")\n- Provide constructive criticism or feedback\n- Ask legitimate questions about trading or the academy\n- Share trading experiences (even losses) in a respectful way\n- Engage in healthy debate about trading strategies\n- Show enthusiasm, excitement, or support for the community\n\n{{ $json.trainingExamples }}\n\nMessages to analyze:\n{{ $json.messageList }}\n\nReturn ONLY a JSON array of message indices that should be deleted.\nFormat: [0, 2, 5]\n\nIf all messages are fine, return: []"
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "85e658f9-cd00-4d64-be35-a2ea56f986d9",
      "name": "update admin channel about moderation",
      "type": "n8n-nodes-base.discord",
      "position": [
        2368,
        256
      ],
      "parameters": {
        "content": "=It seems that a message was deleted for violating community standards.\n\nAuthor: {{ $('Loop Over Items').item.json.author.username }}\nMessage ID: {{ $('Loop Over Items').item.json.messageId }}\nContent: {{ $('Loop Over Items').item.json.content }}",
        "guildId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Loop Over Items').item.json.config.discordServerId }}"
        },
        "options": {},
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Loop Over Items').item.json.config.adminChannelId }}"
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "discordOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d7aba29d-90a4-4dbd-a3ce-51e58395ec5a",
      "name": "delete bad content",
      "type": "n8n-nodes-base.discord",
      "onError": "continueRegularOutput",
      "position": [
        2064,
        256
      ],
      "parameters": {
        "guildId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.config.discordServerId }}"
        },
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.config.moderatedChannelId }}"
        },
        "messageId": "={{ $json.messageId }}",
        "operation": "deleteMessage",
        "authentication": "oAuth2"
      },
      "credentials": {
        "discordOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": false,
      "typeVersion": 2
    },
    {
      "id": "6793f1ac-84df-4ac5-a8db-be75592ebedd",
      "name": "GPT5 mini",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1280,
        320
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini-2025-08-07",
          "cachedResultName": "gpt-5-mini-2025-08-07"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4d84bf71-7fd9-4848-8304-0e8f34495f3b",
      "name": "Get only the bad messages",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        240
      ],
      "parameters": {
        "jsCode": "// Get the AI's response from the current input\nconst aiOutput = $input.first().json.output;\n\n// Get the original messages from the prep node\nconst allMessages = $('prep messages for AI').first().json.originalMessages;\n\n// Get configuration from prep node\nconst config = $('prep messages for AI').first().json.config;\n\n// Debug logs\nconsole.log('AI Output:', aiOutput);\nconsole.log('All Messages:', allMessages);\n\n// Parse the AI response\nlet badIndices = [];\ntry {\n  if (Array.isArray(aiOutput)) {\n    badIndices = aiOutput;\n  } else if (typeof aiOutput === 'string') {\n    badIndices = JSON.parse(aiOutput);\n  }\n} catch (error) {\n  console.log('Parse error:', error);\n  return [];\n}\n\nconsole.log('Bad Indices (before dedup):', badIndices);\n\n// Remove duplicates from badIndices\nbadIndices = [...new Set(badIndices)];\n\nconsole.log('Bad Indices (after dedup):', badIndices);\n\n// Validate indices exist in the array\nbadIndices = badIndices.filter(index => \n  index >= 0 && index < allMessages.length && allMessages[index]\n);\n\nconsole.log('Bad Indices (validated):', badIndices);\n\n// Get only the unique bad messages\nconst messagesToDelete = badIndices.map(index => ({\n  json: {\n    messageId: allMessages[index].id,\n    content: allMessages[index].content,\n    author: allMessages[index].author,\n    config: config  // Pass config forward\n  }\n}));\n\n// Additional safety: Remove duplicate message IDs\nconst uniqueMessages = [];\nconst seenIds = new Set();\n\nfor (const msg of messagesToDelete) {\n  if (!seenIds.has(msg.json.messageId)) {\n    seenIds.add(msg.json.messageId);\n    uniqueMessages.push(msg);\n  }\n}\n\nconsole.log('Final unique messages to delete:', uniqueMessages.length);\n\nreturn uniqueMessages.length > 0 ? uniqueMessages : [];"
      },
      "typeVersion": 2
    },
    {
      "id": "d7990994-24d8-4ac1-b707-f47779be5737",
      "name": "Get sheet knowledgebase",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        640,
        240
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xodthGg8RpQJB62mB6fziuwblG9nn3udZvQvyRquCXM/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1xodthGg8RpQJB62mB6fziuwblG9nn3udZvQvyRquCXM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xodthGg8RpQJB62mB6fziuwblG9nn3udZvQvyRquCXM/edit?usp=drivesdk",
          "cachedResultName": "discord-AI-moderator-good-bad-samples"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "71656c51-2ec8-4452-8ef4-58721e975678",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        -624
      ],
      "parameters": {
        "width": 432,
        "height": 820,
        "content": "# AI-Driven Discord Moderation with Google Sheets Learning & Admin Insights\n\n## How it works:\n\nThis workflow monitors your Discord channel every 3 minutes and uses AI to detect rule-breaking messages. It learns from examples in a Google Sheet to understand what should be deleted (spam, harassment, toxic behavior) vs what's okay (even if it includes profanity in a positive context). Flagged messages are auto-deleted and logged to an admin channel.\n\n## Setup steps\n1. **Copy the training sheet**: [Make a copy of this Google Sheet](https://docs.google.com/spreadsheets/d/1xodthGg8RpQJB62mB6fziuwblG9nn3udZvQvyRquCXM/edit?usp=sharing)\n2. **Add your examples**: Fill in message_content, should_delete (YES/NO), and reason columns\n3. **Set Discord IDs**: Edit the \"Edit Fields\" node with your server ID, monitored channel ID, and admin channel ID\n4. **Connect credentials**: Link Discord OAuth2, Google Sheets, and OpenAI API\n5. **Customize the AI prompt**: Adjust the system message in \"AI Agent\" to match your community rules\n6. **Test**: Run manually first, check admin channel for logs\n\nThe AI understands context and intent, not just keywords."
      },
      "typeVersion": 1
    },
    {
      "id": "da0b2d16-e6ad-4123-8557-1adc2fb4b44b",
      "name": "Training Data Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        16
      ],
      "parameters": {
        "color": 4,
        "width": 224,
        "height": 448,
        "content": "## Training Data\nLoads examples from Google Sheets (message_content, should_delete, reason) to teach the AI your moderation standards."
      },
      "typeVersion": 1
    },
    {
      "id": "e1753b53-7c88-4a60-870b-1b5d732b8f07",
      "name": "Analysis Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        832,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 448,
        "content": "## Message Analysis\nFetches recent messages, formats them with training data, and sends to GPT-5 for context-aware moderation."
      },
      "typeVersion": 1
    },
    {
      "id": "b8ddcc67-1c42-4091-a512-b56f2d07d64c",
      "name": "Processing Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 944,
        "height": 448,
        "content": "## Processing & Cleanup\nParses AI results, deduplicates, then loops through each flagged message to delete and notifies admin channel."
      },
      "typeVersion": 1
    },
    {
      "id": "51453ec4-1a0d-48f2-ad65-bbe24bfa63ea",
      "name": "Get recent messages",
      "type": "n8n-nodes-base.discord",
      "position": [
        896,
        240
      ],
      "parameters": {
        "limit": 10,
        "guildId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Credentials Here').item.json['discord server ID'] }}"
        },
        "options": {},
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Credentials Here').item.json['discord moderated channel ID'] }}"
        },
        "operation": "getAll",
        "authentication": "oAuth2"
      },
      "credentials": {
        "discordOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "16857487-962f-44bf-b0cf-abf5d4864a14",
      "name": "prep messages for AI",
      "type": "n8n-nodes-base.code",
      "position": [
        1104,
        240
      ],
      "parameters": {
        "jsCode": "// ===== CONFIGURATION - EDIT THESE VALUES =====\nconst CONFIG = {\n  discordServerId: '1373767704351346689',        // Your Discord Server ID\n  moderatedChannelId: '1429758114948841592',     // Channel to monitor\n  adminChannelId: '1427336562240126976'          // Admin notification channel\n};\n// YOUR_AWS_SECRET_KEY_HERE====\n\n// Get training examples from Google Sheets (if available)\nlet badExamples = '';\nlet goodExamples = '';\n\ntry {\n  const examples = $('Get sheet knowledgebase').all().map(item => item.json);\n  \n  // Format bad examples (messages to DELETE)\n  const badList = examples\n    .filter(e => e.should_delete === 'YES')\n    .map(e => `\"${e.message_content}\" (Reason: ${e.reason})`)\n    .join('\\n- ');\n  \n  if (badList) {\n    badExamples = `\\n\\nMessages to ALWAYS DELETE:\\n- ${badList}`;\n  }\n  \n  // Format good examples (messages to KEEP)\n  const goodList = examples\n    .filter(e => e.should_delete === 'NO')\n    .map(e => `\"${e.message_content}\" (Reason: ${e.reason})`)\n    .join('\\n- ');\n  \n  if (goodList) {\n    goodExamples = `\\n\\nMessages to NEVER DELETE:\\n- ${goodList}`;\n  }\n} catch (error) {\n  console.log('No training examples found or Google Sheets not connected:', error);\n}\n\n// Get all messages from Discord\nconst messages = $input.all().map(item => item.json);\n\n// Create a formatted list with IDs for the AI\nconst messageList = messages.map((msg, index) => \n  `[${index}] ID: ${msg.id}\\nAuthor: ${msg.author.username}\\nContent: ${msg.content}`\n).join('\\n\\n---\\n\\n');\n\nreturn [{\n  json: {\n    messageList,\n    originalMessages: messages,\n    trainingExamples: badExamples + goodExamples,\n    // Pass config along with the data\n    config: CONFIG\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "aa9274ce-2432-4868-a03b-579aacee5bd2",
      "name": "wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        2224,
        256
      ],
      "parameters": {
        "amount": 1.5
      },
      "typeVersion": 1.1
    },
    {
      "id": "53de222e-5f41-4bd2-9ed8-9c3155637f74",
      "name": "Set Credentials Here",
      "type": "n8n-nodes-base.set",
      "position": [
        416,
        240
      ],
      "parameters": {
        "options": {
          "ignoreConversionErrors": true
        },
        "assignments": {
          "assignments": [
            {
              "id": "38a218af-86fc-4b60-9075-3efe1a3b8262",
              "name": "discord server ID",
              "type": "object",
              "value": "=1234567"
            },
            {
              "id": "94e5554f-5352-40fc-b439-0db6f6ca7da7",
              "name": "discord moderated channel ID",
              "type": "object",
              "value": "1234567"
            },
            {
              "id": "5ffdeb80-179f-49f1-9abf-8bce6c855c13",
              "name": "=discord admin channel ID",
              "type": "object",
              "value": "=1234567"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b9a33dd8-a931-44f3-828e-7c9c58d5dcc5",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1840,
        240
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "850ac17c-a6d9-4651-973b-e65c62a57e82",
  "connections": {
    "wait": {
      "main": [
        [
          {
            "node": "update admin channel about moderation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Get only the bad messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT5 mini": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "delete bad content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set Credentials Here",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "delete bad content": {
      "main": [
        [
          {
            "node": "wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get recent messages": {
      "main": [
        [
          {
            "node": "prep messages for AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Credentials Here": {
      "main": [
        [
          {
            "node": "Get sheet knowledgebase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "prep messages for AI": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get sheet knowledgebase": {
      "main": [
        [
          {
            "node": "Get recent messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get only the bad messages": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "update admin channel about moderation": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}