{
  "nodes": [
    {
      "id": "f1b97dbf-fd7e-4f88-ac2d-71f124559ce4",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1200,
        304
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "29bae06a-2e7a-4a20-8e14-90ecbd1c54ea",
      "name": "Send Acknowledgment",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -960,
        304
      ],
      "parameters": {
        "text": "\ud83c\udfa8 Got it! Generating your image, give me a moment...",
        "chatId": "={{ $json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.2
    },
    {
      "id": "6a34ee3a-10e1-4d22-8502-f4738c50f8cf",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -720,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-01",
              "name": "chatId",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            },
            {
              "id": "cfg-02",
              "name": "userMessage",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.text }}"
            },
            {
              "id": "cfg-03",
              "name": "systemPrompt",
              "type": "string",
              "value": "You are an expert image prompt engineer for Nano Banana 2 (Gemini 3.1 Flash Image Preview). Convert any user request into a Dense Narrative JSON prompt that produces hyper-realistic, unretouched images by neutralizing the model's native AI biases. Output ONLY a valid JSON object \u2014 no markdown fences, no explanation.\n\nREQUIRED JSON STRUCTURE:\n{\n  \"prompt\": \"single dense paragraph \u2014 see PROMPT RULES\",\n  \"negative_prompt\": \"long comma-separated list \u2014 see NEGATIVE PROMPT RULES\",\n  \"settings\": {\n    \"resolution\": \"1024x1792 portrait / 1792x1024 landscape / 1024x1024 square\",\n    \"style\": \"e.g. photorealistic, documentary realism, candid amateur\",\n    \"lighting\": \"describe what the light physically DOES to surfaces, not just the source name\",\n    \"camera_angle\": \"e.g. eye level, slight high angle, low angle\",\n    \"depth_of_field\": \"e.g. shallow \u2014 subject sharp, background bokeh\",\n    \"quality\": \"e.g. high detail, unretouched skin, visible film grain\"\n  }\n}\n\nPROMPT RULES \u2014 apply ALL:\n1. CAMERA MATH: Always specify exact focal length, aperture, ISO. Example: 85mm lens, f/1.8, ISO 400. Keep ISO below 800 \u2014 higher values trigger digital art bias.\n2. EXPLICIT IMPERFECTIONS (humans): Include 1\u20132 subtle, natural details that vary per subject. Choose from: visible pores, slight asymmetry, natural skin texture, soft under-eye shadows, peach fuzz. Avoid stacking multiple flaws \u2014 one or two well-chosen details are enough for realism.\n3. LIGHTING BEHAVIOR: Describe what the light does, not what it is. Bad: natural light. Good: soft overcast daylight diffusing evenly across the face, eliminating harsh shadows while leaving skin texture fully visible.\n4. DIRECT COMMANDS INSIDE PROMPT: End the prompt with one light directive. Example: Render natural skin texture as captured by the lens. Do not apply beauty filters or artificial smoothing.\n5. NON-HUMAN SUBJECTS: Replace skin logic with material physics. Define surface texture (micro-scratches on anodized aluminum), light interaction (subsurface scattering through translucent petals), wear-and-tear (worn leather edges). For labels: specify exact text string and font style.\n6. LENS IMPERFECTIONS (embed when relevant): subtle chromatic aberration at high-contrast edges, slight vignetting in corners, minor barrel distortion for wide angle.\n7. NOISE TRAP: Do NOT use ISO above 800 or heavy film grain in high-contrast scenes \u2014 this triggers illustration bias. Use physical subject imperfections instead.\n\nNEGATIVE PROMPT RULES \u2014 always include ALL these blockers:\nSkin/beauty: plastic skin, airbrushed texture, skin smoothing, beautification filters, heavy makeup, unrealistic skin, porcelain skin\nAnatomy averaging: anatomy normalization, body proportion averaging, dataset-average anatomy, editorial fashion proportions, idealized proportions\nOptical distortions: wide-angle distortion not in reference, lens compression not in reference, depth flattening, cropping that removes volume, mirror selfies, reflections\nGeneral: blurry, low resolution, distorted face, extra fingers, overexposed, cartoon, CGI, 3D render, illustration, painting, oversaturated colors, watermark\nStyle drift: stylized realism, digital art, airbrushed, stock photo look, studio backdrop\n\nADAPTIVE BEHAVIOR:\n- Multi-panel requests: add multi_panel_layout array with panel, pose, action per panel.\n- Reference image provided: add image_input array with URL(s).\n- Drop fields that do not apply. A landscape has no outfit block. A product shot has no skin block."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a105cf67-99c8-49ef-9a28-27d0dc47279b",
      "name": "Expand to JSON Prompt",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -480,
        304
      ],
      "parameters": {
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ model: 'google/gemini-3.1-pro-preview', messages: [{ role: 'system', content: $json.systemPrompt }, { role: 'user', content: $json.userMessage }], max_tokens: 4000 }) }}\n\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi"
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "f7fe310b-8a01-4e96-a1c1-9de2eb002077",
      "name": "Parse and Prepare",
      "type": "n8n-nodes-base.code",
      "position": [
        -240,
        288
      ],
      "parameters": {
        "jsCode": "const content = $json.choices[0].message.content;\nconst chatId = $('Config').first().json.chatId;\n\nconst cleaned = content\n  .replace(/^```(?:json)?\\s*/i, '')\n  .replace(/```\\s*$/i, '')\n  .trim();\n\nlet promptObj;\ntry {\n  promptObj = JSON.parse(cleaned);\n} catch(e) {\n  throw new Error('LLM did not return valid JSON. Got: ' + cleaned.slice(0, 300));\n}\n\nif (!promptObj.prompt) throw new Error('LLM response missing the \"prompt\" field');\n\nreturn [{ json: {\n  chatId: String(chatId),\n  imagePrompt: JSON.stringify(promptObj)\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "510871c8-ad50-4d25-89b7-48a2f6e80ccb",
      "name": "Generate Image",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -48,
        272
      ],
      "parameters": {
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ model: 'google/gemini-3.1-flash-image-preview', messages: [{ role: 'user', content: 'Generate an image: ' + $json.imagePrompt }], modalities: ['image', 'text'], max_tokens: 2000 }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi"
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "8f360468-f104-4292-8670-8f9faf78f069",
      "name": "Send Photo to Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        576,
        128
      ],
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "operation": "sendPhoto",
        "binaryData": true,
        "additionalFields": {
          "caption": "\u2705 Here's your generated image!"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1483b797-4b7f-403d-8923-29d31870f9fd",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        0
      ],
      "parameters": {
        "width": 456,
        "height": 752,
        "content": "## Generate hyper-realistic images from Telegram messages with Nano Banana 2\n\nSend any image description to your Telegram bot and receive a hyper-realistic AI-generated photo back in seconds. The workflow uses a two-step AI pipeline: Gemini Pro first transforms your plain-text request into a structured Dense Narrative JSON prompt (camera math, lighting physics, imperfection rules), then Gemini Flash generates the final image synchronously via OpenRouter.\n\n### How it works\n1. A user sends a natural language image request to the Telegram bot\n2. The bot immediately confirms receipt\n3. Gemini Pro expands the request into a detailed JSON prompt: focal length, aperture, ISO, lighting behavior, material physics, etc.\n4. Gemini Flash generates the image and returns it as a base64 string in the same API response\n5. The image is decoded, converted to a binary file, and sent back as a photo in the same Telegram chat\n\n### Setup steps\n1. **Telegram** \u2014 Create a bot via @BotFather, copy the token, and add it as a Telegram credential in n8n. Connect it to the Telegram Trigger node and both Telegram send nodes\n2. **OpenRouter** \u2014 Add your OpenRouter API key as a credential in n8n. Connect it to the Expand to JSON Prompt and Generate Image nodes.\n3. Activate the workflow and send your bot an image description to test"
      },
      "typeVersion": 1
    },
    {
      "id": "64f1d8c3-89ad-4252-aa45-7dfe9356f0f5",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1216,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 124,
        "content": "## Trigger & Acknowledgment\nReceives the Telegram message and immediately sends a confirmation reply."
      },
      "typeVersion": 1
    },
    {
      "id": "f08bef29-4a29-458c-a85a-84da474ada1c",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 564,
        "height": 124,
        "content": "## Prompt Engineering\nThe Config node stores the system prompt and user input. Gemini Pro expands the plain-text request into a full Dense Narrative JSON prompt following the Nano Banana 2 schema."
      },
      "typeVersion": 1
    },
    {
      "id": "02ebc309-9558-4ec0-8e67-39a14584533b",
      "name": "Upload to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        576,
        288
      ],
      "parameters": {
        "name": "=image.png",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive",
          "cachedResultUrl": "https://drive.google.com/drive/my-drive",
          "cachedResultName": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "1CYgRUELLCpM1tHx5cM6hBpf5RdfxItR0",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1CYgRUELLCpM1tHx5cM6hBpf5RdfxItR0",
          "cachedResultName": "AI images"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "c1c6485c-df10-419f-831d-27f23fca6ab4",
      "name": "Send Failure Notification",
      "type": "n8n-nodes-base.telegram",
      "position": [
        224,
        400
      ],
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "operation": "sendPhoto",
        "binaryData": true,
        "additionalFields": {
          "caption": "\u274c Image generation failed! Please try again."
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "50c265f8-917b-477d-9574-ab13fa9b22d9",
      "name": "Send Failure Notification1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -256,
        624
      ],
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "operation": "sendPhoto",
        "binaryData": true,
        "additionalFields": {
          "caption": "\u274c Image generation failed! Please try again."
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2d4a3c45-86b1-4e61-8a21-599680b2ef3f",
      "name": "Convert to File",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        368,
        176
      ],
      "parameters": {
        "options": {},
        "operation": "toBinary",
        "sourceProperty": "image_file"
      },
      "typeVersion": 1.1
    },
    {
      "id": "54e03022-8bcb-455e-b659-ac3a5d0ec35a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 944,
        "height": 128,
        "content": "## Image Generation & Delivery\nGemini Flash generates the image via OpenRouter and returns a base64 URL. The Get Image URL node extracts it, Convert to File decodes it to binary, then the image is sent directly to Telegram and uploaded to Google Drive."
      },
      "typeVersion": 1
    },
    {
      "id": "5e44f480-020d-4e2b-875f-9ee7432405c0",
      "name": "Get Image URL",
      "type": "n8n-nodes-base.set",
      "position": [
        192,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4409adb8-cde7-450d-82b3-f9224b32787a",
              "name": "image_file",
              "type": "string",
              "value": "={{ $json.choices[0].message.images[0].image_url.url.split(\",\")[1] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Expand to JSON Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Image URL": {
      "main": [
        [
          {
            "node": "Convert to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image": {
      "main": [
        [
          {
            "node": "Get Image URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Failure Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to File": {
      "main": [
        [
          {
            "node": "Send Photo to Telegram",
            "type": "main",
            "index": 0
          },
          {
            "node": "Upload to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Send Acknowledgment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Prepare": {
      "main": [
        [
          {
            "node": "Generate Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Acknowledgment": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand to JSON Prompt": {
      "main": [
        [
          {
            "node": "Parse and Prepare",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Failure Notification1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Google Drive": {
      "main": [
        []
      ]
    }
  }
}