{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "05e93c77-cf6a-4f19-8261-3e7593d13ed4",
      "name": "Sticky Note - Main",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2512,
        816
      ],
      "parameters": {
        "width": 440,
        "height": 1308,
        "content": "##  Visual Daily Journal\n\n**Automatic diary entries from your Discord conversations**\n\nEvery day at your chosen time, this workflow:\n1. Fetches messages from a Discord channel\n2. Summarizes your day with GPT-4\n3. Generates a representative image with DALL-E\n4. Creates a beautiful entry in Notion\n\n---\n\n### How it works\n\n1. **Schedule Trigger** runs daily at your chosen time (default: 11pm)\n2. **Discord node** fetches the last 100 messages from your channel\n3. **Filter node** keeps only today's messages\n4. **Check node** skips if no messages found\n5. **GPT-4** analyzes conversations and creates:\n   - Creative title\n   - Reflective summary\n   - Mood assessment\n   - Relevant tags\n   - Image prompt\n6. **DALL-E 3** generates an artistic image for the day\n7. **Cloudinary** uploads the image (Notion needs a public URL)\n8. **Notion** creates a beautiful diary entry with everything\n\n---\n\n### Setup steps (15-20 min)\n\n1. **Discord credentials**\n   - Go to discord.com/developers/applications\n   - Create New Application \u2192 Bot \u2192 Copy Token\n   - Add bot to server with Read Message History permission\n   - In n8n: Settings \u2192 Credentials \u2192 Discord Bot API\n\n2. **OpenAI credentials**\n   - Settings \u2192 Credentials \u2192 OpenAI API\n\n3. **Cloudinary** (free account)\n   - Sign up at cloudinary.com\n   - Copy Cloud Name, API Key, API Secret\n   - Update values in \"Upload to Cloudinary\" node\n\n4. **Notion credentials**\n   - Create integration at notion.so/my-integrations\n   - Settings \u2192 Credentials \u2192 Notion API\n   - Share your database with the integration\n\n5. **Configure nodes**\n   - Select your Discord server and channel\n   - Update OPENAI_API_KEY in DALL-E node\n   - Set your Notion database ID"
      },
      "typeVersion": 1
    },
    {
      "id": "80279469-95ca-4ce7-82b0-19797b0215f0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3008,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 160,
        "height": 224,
        "content": "**Step 1: Schedule**\nDaily trigger"
      },
      "typeVersion": 1
    },
    {
      "id": "56796dd8-6c4b-4bff-951f-932fe7095cd8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 180,
        "height": 224,
        "content": "**Step 2: Discord**\nOfficial node"
      },
      "typeVersion": 1
    },
    {
      "id": "4b339b36-7efe-404c-a73f-eeec51b21f2b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3408,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 196,
        "height": 224,
        "content": "**Step 3: Filter**\nToday's messages"
      },
      "typeVersion": 1
    },
    {
      "id": "5601ab8a-7e26-427f-833e-d200ce5d2ee3",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3648,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 160,
        "height": 224,
        "content": "**Step 4: Check**\nHas messages?"
      },
      "typeVersion": 1
    },
    {
      "id": "e442bc4c-5974-4114-bded-831d35dfc7bb",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3840,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 176,
        "height": 208,
        "content": "**Step 5: Format**\nPrepare text"
      },
      "typeVersion": 1
    },
    {
      "id": "1eca36c0-1c38-4eb7-b432-4f34f555d496",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4032,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 196,
        "height": 240,
        "content": "**Step 6: GPT-4**\nAnalyze day"
      },
      "typeVersion": 1
    },
    {
      "id": "35baedcf-01c2-4302-96f5-92afd00e0463",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4256,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 160,
        "content": "**Step 7: Parse**\nExtract data"
      },
      "typeVersion": 1
    },
    {
      "id": "ecba201d-d6c6-4a23-a97f-1c3396e5997a",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4464,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 180,
        "height": 224,
        "content": "**Step 8: DALL-E**\nGenerate image"
      },
      "typeVersion": 1
    },
    {
      "id": "7c3452c2-9d92-4541-baa5-85f74cf7c6a1",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4672,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 196,
        "height": 224,
        "content": "**Step 9: Upload**\nCloudinary"
      },
      "typeVersion": 1
    },
    {
      "id": "59ee1280-1530-414b-9017-b41185aaa7b8",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4896,
        928
      ],
      "parameters": {
        "color": 7,
        "width": 196,
        "height": 224,
        "content": "**Step 10: Notion**\nCreate entry"
      },
      "typeVersion": 1
    },
    {
      "id": "df177715-c451-4120-be5d-4ec306d2a45c",
      "name": "Daily Trigger (11pm)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3040,
        1008
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "888d0e4d-77bf-43a3-8b7e-1f1ee083c511",
      "name": "Get Discord Messages",
      "type": "n8n-nodes-base.discord",
      "position": [
        3248,
        1008
      ],
      "parameters": {
        "guildId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {},
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "operation": "getAll"
      },
      "typeVersion": 2
    },
    {
      "id": "74f3c044-3985-42be-b802-4c9cafef1778",
      "name": "Filter Today's Messages",
      "type": "n8n-nodes-base.code",
      "position": [
        3456,
        1008
      ],
      "parameters": {
        "jsCode": "// Filter messages from today only\nconst allMessages = $input.all();\nconst now = new Date();\nconst todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);\nconst todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);\n\n// Filter to today's messages\nconst todayMessages = allMessages.filter(item => {\n  const msgDate = new Date(item.json.timestamp);\n  return msgDate >= todayStart && msgDate <= todayEnd;\n});\n\n// Format the messages\nconst formattedMessages = todayMessages.map(item => ({\n  id: item.json.id,\n  content: item.json.content || '',\n  author: item.json.author?.username || 'Unknown',\n  timestamp: item.json.timestamp,\n  attachments: item.json.attachments?.length || 0\n})).reverse(); // Chronological order\n\nreturn {\n  json: {\n    messages: formattedMessages,\n    messageCount: formattedMessages.length,\n    date: now.toISOString().split('T')[0],\n    fetchedAt: now.toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e96b9987-dbdb-4e44-beaf-b48963e77ef6",
      "name": "Has Messages?",
      "type": "n8n-nodes-base.if",
      "position": [
        3680,
        1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-check-messages",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.messageCount }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b1f6d4c8-b876-4e7a-9ee6-abd27b503070",
      "name": "Format for GPT-4",
      "type": "n8n-nodes-base.code",
      "position": [
        3888,
        1008
      ],
      "parameters": {
        "jsCode": "// Format messages for GPT-4 analysis\nconst data = $input.first().json;\nconst messages = data.messages;\n\nif (!messages || messages.length === 0) {\n  return { json: { error: 'No messages to process' } };\n}\n\n// Build conversation text\nconst conversationText = messages.map(msg => {\n  const time = new Date(msg.timestamp).toLocaleTimeString('en-US', {\n    hour: '2-digit',\n    minute: '2-digit'\n  });\n  return `[${time}] ${msg.author}: ${msg.content}`;\n}).join('\\n');\n\n// Extract unique participants\nconst participants = [...new Set(messages.map(m => m.author))];\n\n// Count messages per person\nconst messageCounts = {};\nmessages.forEach(msg => {\n  messageCounts[msg.author] = (messageCounts[msg.author] || 0) + 1;\n});\n\nreturn {\n  json: {\n    conversationText: conversationText,\n    participants: participants,\n    messageCounts: messageCounts,\n    totalMessages: data.messageCount,\n    date: data.date,\n    dateFormatted: new Date(data.date).toLocaleDateString('en-US', {\n      weekday: 'long',\n      year: 'numeric',\n      month: 'long',\n      day: 'numeric'\n    })\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "01b52d05-4eb6-4534-b733-85115d9cd85f",
      "name": "GPT-4 Analyze Day",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4080,
        1008
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"gpt-4o\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a thoughtful journal assistant. Analyze the day's Discord conversations and create a personal diary entry. Be warm, reflective, and capture the essence of the day. Focus on themes, emotions, achievements, and memorable moments.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Here are today's Discord messages ({{ $json.dateFormatted }}):\\n\\n{{ $json.conversationText }}\\n\\nParticipants: {{ $json.participants.join(', ') }}\\nTotal messages: {{ $json.totalMessages }}\\n\\nPlease analyze this and provide:\\n1. A diary entry title (creative, captures the day's essence)\\n2. A reflective summary (2-3 paragraphs, personal tone)\\n3. Mood assessment (one of: Great, Good, Neutral, Low, Productive)\\n4. 3-5 relevant tags (topics discussed)\\n5. An image prompt to visually represent this day (artistic, atmospheric, no text)\"\n    }\n  ],\n  \"response_format\": {\n    \"type\": \"json_schema\",\n    \"json_schema\": {\n      \"name\": \"diary_entry\",\n      \"strict\": true,\n      \"schema\": {\n        \"type\": \"object\",\n        \"additionalProperties\": false,\n        \"properties\": {\n          \"title\": { \"type\": \"string\" },\n          \"summary\": { \"type\": \"string\" },\n          \"mood\": { \"type\": \"string\" },\n          \"tags\": {\n            \"type\": \"array\",\n            \"items\": { \"type\": \"string\" }\n          },\n          \"imagePrompt\": { \"type\": \"string\" },\n          \"highlights\": {\n            \"type\": \"array\",\n            \"items\": { \"type\": \"string\" }\n          }\n        },\n        \"required\": [\"title\", \"summary\", \"mood\", \"tags\", \"imagePrompt\", \"highlights\"]\n      }\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.3
    },
    {
      "id": "e0a6c9e0-46d3-4cb8-a958-fcccb0980682",
      "name": "Parse Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        4304,
        1008
      ],
      "parameters": {
        "jsCode": "// Parse GPT-4 response and prepare for image generation\nconst response = $input.first().json;\nconst previousData = $('Format for GPT-4').first().json;\n\nconst analysis = JSON.parse(response.choices[0].message.content);\n\n// Map mood to emoji\nconst moodEmojis = {\n  'Great': '\ud83d\ude0a',\n  'Good': '\ud83d\ude0c',\n  'Neutral': '\ud83d\ude10',\n  'Low': '\ud83d\ude14',\n  'Productive': '\ud83d\udd25'\n};\n\nconst moodWithEmoji = `${moodEmojis[analysis.mood] || '\ud83d\ude10'} ${analysis.mood}`;\n\nreturn {\n  json: {\n    title: analysis.title,\n    summary: analysis.summary,\n    mood: analysis.mood,\n    moodWithEmoji: moodWithEmoji,\n    tags: analysis.tags,\n    imagePrompt: analysis.imagePrompt,\n    highlights: analysis.highlights,\n    date: previousData.date,\n    dateFormatted: previousData.dateFormatted,\n    totalMessages: previousData.totalMessages,\n    participants: previousData.participants\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b71d6b2f-76a3-40fe-b4c6-2e5ac31e423a",
      "name": "Generate Image (DALL-E)",
      "type": "n8n-nodes-base.code",
      "position": [
        4496,
        1008
      ],
      "parameters": {
        "jsCode": "// ============================================\n// GENERATE IMAGE WITH DALL-E 3\n// ============================================\n// \u26a0\ufe0f UPDATE THIS API KEY\nconst OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY';\n// ============================================\n\nconst data = $input.first().json;\n\n// Enhance the prompt for better artistic results\nconst enhancedPrompt = `${data.imagePrompt}. Artistic digital illustration, dreamy atmosphere, soft lighting, no text or words, suitable for a personal journal.`;\n\ntry {\n  const response = await fetch('https://api.openai.com/v1/images/generations', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${OPENAI_API_KEY}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      model: 'dall-e-3',\n      prompt: enhancedPrompt.substring(0, 4000),\n      n: 1,\n      size: '1024x1024',\n      quality: 'standard',\n      response_format: 'b64_json'\n    })\n  });\n  \n  const result = await response.json();\n  \n  if (result.error) {\n    return {\n      json: {\n        ...data,\n        imageBase64: null,\n        imageError: result.error.message\n      }\n    };\n  }\n  \n  return {\n    json: {\n      ...data,\n      imageBase64: result.data[0].b64_json,\n      imageError: null\n    }\n  };\n  \n} catch (error) {\n  return {\n    json: {\n      ...data,\n      imageBase64: null,\n      imageError: error.message\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "a6b403d9-1ab9-4bcf-a2cb-69862e2f9dac",
      "name": "Upload to Cloudinary",
      "type": "n8n-nodes-base.code",
      "position": [
        4720,
        1008
      ],
      "parameters": {
        "jsCode": "// ============================================\n// UPLOAD IMAGE TO CLOUDINARY\n// ============================================\n// \u26a0\ufe0f UPDATE THESE VALUES\nconst CLOUDINARY_CLOUD_NAME = 'YOUR_CLOUD_NAME';\nconst CLOUDINARY_API_KEY = 'YOUR_API_KEY';\nconst CLOUDINARY_API_SECRET = 'YOUR_API_SECRET';\n// ============================================\n\nconst data = $input.first().json;\n\nif (!data.imageBase64) {\n  return {\n    json: {\n      ...data,\n      imageUrl: null,\n      uploadError: data.imageError || 'No image to upload'\n    }\n  };\n}\n\ntry {\n  // Create signature for Cloudinary\n  const timestamp = Math.floor(Date.now() / 1000);\n  const folder = 'visual-journal';\n  const publicId = `diary_${data.date}`;\n  \n  // Build signature string\n  const signatureString = `folder=${folder}&public_id=${publicId}&timestamp=${timestamp}${CLOUDINARY_API_SECRET}`;\n  \n  // Create SHA1 hash\n  const crypto = require('crypto');\n  const signature = crypto.createHash('sha1').update(signatureString).digest('hex');\n  \n  // Upload to Cloudinary\n  const formData = new URLSearchParams();\n  formData.append('file', `data:image/png;base64,${data.imageBase64}`);\n  formData.append('api_key', CLOUDINARY_API_KEY);\n  formData.append('timestamp', timestamp.toString());\n  formData.append('signature', signature);\n  formData.append('folder', folder);\n  formData.append('public_id', publicId);\n  \n  const response = await fetch(\n    `https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/upload`,\n    {\n      method: 'POST',\n      body: formData\n    }\n  );\n  \n  const result = await response.json();\n  \n  if (result.error) {\n    return {\n      json: {\n        ...data,\n        imageUrl: null,\n        uploadError: result.error.message\n      }\n    };\n  }\n  \n  return {\n    json: {\n      ...data,\n      imageUrl: result.secure_url,\n      imagePublicId: result.public_id,\n      uploadError: null\n    }\n  };\n  \n} catch (error) {\n  return {\n    json: {\n      ...data,\n      imageUrl: null,\n      uploadError: error.message\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "cd529bb8-3553-4e91-b97a-bc4d13f07b22",
      "name": "Create Notion Entry",
      "type": "n8n-nodes-base.notion",
      "position": [
        4944,
        1008
      ],
      "parameters": {
        "blockUi": {
          "blockValues": [
            {
              "type": "image"
            },
            {
              "type": "heading_2",
              "textContent": "\ud83d\udcdd Summary"
            },
            {
              "textContent": "={{ $json.summary }}"
            },
            {
              "type": "heading_2",
              "textContent": "\u2728 Highlights"
            },
            {
              "type": "bulleted_list_item",
              "textContent": "={{ $json.highlights[0] || '' }}"
            },
            {
              "type": "bulleted_list_item",
              "textContent": "={{ $json.highlights[1] || '' }}"
            },
            {
              "type": "bulleted_list_item",
              "textContent": "={{ $json.highlights[2] || '' }}"
            },
            {
              "type": "divider"
            },
            {
              "textContent": "={{ '\ud83d\udc65 Participants: ' + $json.participants.join(', ') }}"
            },
            {
              "textContent": "={{ '\ud83d\udcac Total messages: ' + $json.totalMessages }}"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Title|title",
              "title": "={{ $json.title }}"
            },
            {
              "key": "Date|date",
              "date": "={{ $json.date }}",
              "includeTime": false
            },
            {
              "key": "Summary|rich_text",
              "textContent": "={{ $json.summary }}"
            },
            {
              "key": "Mood|select",
              "selectValue": "={{ $json.moodWithEmoji }}"
            },
            {
              "key": "Message Count|number",
              "numberValue": "={{ $json.totalMessages }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "5cef8e68-3870-47ad-8b88-2ca60945f301",
      "name": "Success Response",
      "type": "n8n-nodes-base.code",
      "position": [
        5168,
        1008
      ],
      "parameters": {
        "jsCode": "// Final response - entry created successfully\nconst data = $input.first().json;\nconst previousData = $('Upload to Cloudinary').first().json;\n\nreturn {\n  json: {\n    success: true,\n    message: 'Diary entry created successfully!',\n    entry: {\n      title: previousData.title,\n      date: previousData.date,\n      mood: previousData.moodWithEmoji,\n      messageCount: previousData.totalMessages,\n      hasImage: !!previousData.imageUrl,\n      notionPageId: data.id,\n      notionUrl: data.url\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "032d91c1-3994-4511-b895-9e8a34030265",
      "name": "No Messages Today",
      "type": "n8n-nodes-base.code",
      "position": [
        3888,
        1184
      ],
      "parameters": {
        "jsCode": "// No messages today - skip entry\nconst data = $input.first().json;\n\nreturn {\n  json: {\n    success: false,\n    message: 'No messages found for today. Skipping diary entry.',\n    date: data.date || new Date().toISOString().split('T')[0]\n  }\n};"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "Has Messages?": {
      "main": [
        [
          {
            "node": "Format for GPT-4",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Messages Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Analysis": {
      "main": [
        [
          {
            "node": "Generate Image (DALL-E)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format for GPT-4": {
      "main": [
        [
          {
            "node": "GPT-4 Analyze Day",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-4 Analyze Day": {
      "main": [
        [
          {
            "node": "Parse Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Notion Entry": {
      "main": [
        [
          {
            "node": "Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Trigger (11pm)": {
      "main": [
        [
          {
            "node": "Get Discord Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Discord Messages": {
      "main": [
        [
          {
            "node": "Filter Today's Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Cloudinary": {
      "main": [
        [
          {
            "node": "Create Notion Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Today's Messages": {
      "main": [
        [
          {
            "node": "Has Messages?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image (DALL-E)": {
      "main": [
        [
          {
            "node": "Upload to Cloudinary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}