This workflow corresponds to n8n.io template #7655 — we link there as the canonical source.
This workflow follows the Agent → OpenRouter Chat recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "TfKEOWRNsYI093kR",
"name": "GiggleGPTBot Template",
"tags": [],
"nodes": [
{
"id": "c4dbddca-9165-401f-9a96-995a958a425a",
"name": "Webhook Telegram",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-1136,
64
],
"parameters": {
"updates": [
"*"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5369e144-45cd-4a99-9fa6-284ae24ec585",
"name": "OpenRouter Commands",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
-512,
736
],
"parameters": {
"model": "openai/gpt-oss-120b",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5ee25a3f-a9c7-4994-a9a9-315b9eeec5af",
"name": "Init Database",
"type": "n8n-nodes-base.postgres",
"position": [
-1248,
-128
],
"parameters": {
"query": "CREATE TABLE IF NOT EXISTS user_messages (\n id SERIAL PRIMARY KEY,\n user_id BIGINT NOT NULL,\n username VARCHAR(255),\n first_name VARCHAR(255),\n chat_id BIGINT NOT NULL,\n message_text TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS bot_responses (\n id SERIAL PRIMARY KEY,\n user_id BIGINT NOT NULL,\n chat_id BIGINT NOT NULL,\n user_message TEXT,\n bot_response TEXT,\n response_type VARCHAR(50),\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS bot_commands (\n id SERIAL PRIMARY KEY,\n user_id BIGINT NOT NULL,\n chat_id BIGINT NOT NULL,\n command VARCHAR(50) NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS message_reactions (\n id SERIAL PRIMARY KEY,\n message_id BIGINT NOT NULL,\n chat_id BIGINT NOT NULL,\n user_id BIGINT NOT NULL,\n emoji VARCHAR(10),\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS scheduled_posts (\n id SERIAL PRIMARY KEY,\n chat_id BIGINT NOT NULL,\n message_text TEXT,\n post_type VARCHAR(50),\n scheduled_time TIME,\n is_active BOOLEAN DEFAULT true,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE user_stats (\n user_id BIGINT NOT NULL,\n chat_id BIGINT NOT NULL,\n messages_count INT DEFAULT 0,\n commands_count INT DEFAULT 0,\n reactions_received INT DEFAULT 0,\n last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (user_id, chat_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_user_messages_chat_user ON user_messages(chat_id, user_id);\nCREATE INDEX IF NOT EXISTS idx_bot_responses_chat_user ON bot_responses(chat_id, user_id);\nCREATE INDEX IF NOT EXISTS idx_commands_user ON bot_commands(user_id, command);\nCREATE INDEX IF NOT EXISTS idx_reactions_message ON message_reactions(message_id);\nCREATE INDEX idx_user_stats_activity ON user_stats(last_activity DESC);\nCREATE INDEX idx_user_stats_messages ON user_stats(messages_count DESC);\n",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "2f7789fd-645c-436f-91ef-04cb2143aa7d",
"name": "Log message + statistics",
"type": "n8n-nodes-base.postgres",
"onError": "continueRegularOutput",
"position": [
-960,
-192
],
"parameters": {
"query": "INSERT INTO user_messages (user_id, username, first_name, chat_id, message_text) \nVALUES ({{ $('Webhook Telegram').item.json.message.from.id }}, \n '{{ ($('Webhook Telegram').item.json.message.from.username || '').replace(/'/g, \"''\") }}', \n '{{ ($('Webhook Telegram').item.json.message.from.first_name || '').replace(/'/g, \"''\") }}', \n {{ $('Webhook Telegram').item.json.message.chat.id }}, \n '{{ ($('Webhook Telegram').item.json.message.text || '').replace(/'/g, \"''\") }}');\n\nINSERT INTO user_stats (user_id, chat_id, messages_count, last_activity)\nVALUES ({{ $('Webhook Telegram').item.json.message.from.id }}, \n {{ $('Webhook Telegram').item.json.message.chat.id }}, \n 1, CURRENT_TIMESTAMP)\nON CONFLICT (user_id, chat_id) \nDO UPDATE SET \n messages_count = user_stats.messages_count + 1,\n last_activity = CURRENT_TIMESTAMP;",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "5676c8d7-a456-4f12-be6a-2cd5c2ec41e6",
"name": "Adding a schedule",
"type": "n8n-nodes-base.postgres",
"position": [
-1120,
400
],
"parameters": {
"query": "INSERT INTO scheduled_posts (chat_id, post_type, scheduled_time) VALUES\n(-1+1234567890, 'morning_joke', '06:00:00'),\n(-1+1234567890, 'random_wisdom', '17:00:00'),\n(-1+1234567890, 'daily_motivation', '09:00:00');",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "5b7c3300-fa5a-43f5-881f-db48314be81c",
"name": "Schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-992,
544
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 * * * *"
}
]
}
},
"typeVersion": 1
},
{
"id": "756e467d-835a-4166-8972-4edf50b0007f",
"name": "Get scheduled posts",
"type": "n8n-nodes-base.postgres",
"position": [
-800,
544
],
"parameters": {
"query": "SELECT chat_id, post_type \nFROM scheduled_posts \nWHERE is_active = true \nAND EXTRACT(HOUR FROM scheduled_time) = EXTRACT(HOUR FROM CURRENT_TIME);",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "84247455-9eed-424d-8ee4-84ece8f10d54",
"name": "Chat history",
"type": "n8n-nodes-base.postgres",
"position": [
-768,
-96
],
"parameters": {
"query": "SELECT \n message_text,\n username,\n first_name,\n created_at,\n 'user' as message_type\nFROM user_messages \nWHERE chat_id = {{ $('Webhook Telegram').item.json.message.chat.id }}\n\nUNION ALL\n\nSELECT \n bot_response as message_text,\n 'GiggleGPTBot' as username,\n 'GiggleGPTBot' as first_name,\n created_at,\n 'bot' as message_type\nFROM bot_responses \nWHERE chat_id = {{ $('Webhook Telegram').item.json.message.chat.id }}\n\nORDER BY created_at DESC \nLIMIT 15;",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "86872198-4d19-4c3e-b5dd-14dda87a9139",
"name": "Mention Analysis",
"type": "n8n-nodes-base.code",
"position": [
-592,
-96
],
"parameters": {
"jsCode": "const telegramData = $('Webhook Telegram').first().json;\nconst chatHistory = $('Chat history').all();\n\nconst userId = telegramData.message.from.id;\nconst userName = telegramData.message.from.first_name || telegramData.message.from.username || 'Anonymous';\nlet userMessage = telegramData.message.text;\nconst chatId = telegramData.message.chat.id;\n\nconst commandMatch = userMessage.match(/^\\/(\\w+)(?:@\\w+)?(?:\\s+(.*))?/);\nconst command = commandMatch ? commandMatch[1].toLowerCase() : '';\n\nconst originalMessage = userMessage;\nuserMessage = userMessage.replace(/@GiggleGPTBot/gi, '').trim();\nconst isEmptyAfterTag = !userMessage || userMessage.length === 0;\n\nconst recentMessages = chatHistory.map(item => {\n const msgText = item.json.message_text || '';\n const author = item.json.message_type === 'bot' ? 'GiggleGPTBot' : \n (item.json.first_name || item.json.username || 'Anonymous');\n return `${author}: ${msgText}`;\n}).slice(0, 15).join('\\n'); \n\nlet contentType = 'mixed';\nconst lowerMessage = userMessage.toLowerCase();\nif (lowerMessage.includes('joke') || lowerMessage.includes('funny')) {\n contentType = 'funny';\n} else if (lowerMessage.includes('motivat') || lowerMessage.includes('motivat')) {\n contentType = 'inspiring';\n}\n\nif (isEmptyAfterTag) {\n const types = ['funny', 'inspiring', 'mixed'];\n contentType = types[Math.floor(Math.random() * types.length)];\n}\n\nconst now = new Date();\nconst hour = now.getHours();\nlet timeContext = '';\nif (hour < 6) timeContext = 'night';\nelse if (hour < 12) timeContext = 'morning';\nelse if (hour < 18) timeContext = 'day';\nelse timeContext = 'evening';\n\nreturn [{\n json: {\n userId,\n userName,\n userMessage: isEmptyAfterTag ? 'Random phrase' : userMessage,\n originalMessage,\n chatId,\n contentType,\n command,\n timeContext,\n recentMessages,\n chatType: telegramData.message.chat.type,\n isRandomRequest: isEmptyAfterTag\n }\n}];"
},
"typeVersion": 2
},
{
"id": "60de834b-47c8-428a-9d28-87ade8fa8a43",
"name": "Get user statistics",
"type": "n8n-nodes-base.postgres",
"position": [
-768,
64
],
"parameters": {
"query": "SELECT \n us.user_id,\n us.messages_count,\n us.commands_count,\n 0 as reactions_received,\n us.last_activity,\n (SELECT COUNT(*) FROM bot_responses br WHERE br.user_id = us.user_id AND br.chat_id = us.chat_id) as responses_count\nFROM user_stats us\nWHERE us.chat_id = {{ $json.message.chat.id }}\n AND us.user_id = {{ $json.message.from.id }}\nLIMIT 1;",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "d57380bc-5dc5-4130-a767-463bbf81931c",
"name": "Get top users",
"type": "n8n-nodes-base.postgres",
"position": [
-768,
224
],
"parameters": {
"query": "SELECT \n us.messages_count,\n us.commands_count,\n CONCAT(\n COALESCE((SELECT first_name FROM user_messages WHERE user_id = us.user_id LIMIT 1), 'User'),\n ' (@',\n COALESCE((SELECT username FROM user_messages WHERE user_id = us.user_id LIMIT 1), 'no_username'),\n ')'\n ) as user_name\nFROM user_stats us\nWHERE us.chat_id = {{ $json.message.from.id }}\n AND us.messages_count > 0\nORDER BY (us.messages_count + us.commands_count * 2) DESC\nLIMIT 10;",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "b12f28fc-6fb0-45ca-8348-202bf5a78332",
"name": "AI response to command",
"type": "@n8n/n8n-nodes-langchain.agent",
"maxTries": 2,
"position": [
-240,
-80
],
"parameters": {
"text": "=Command: /{{ $json.command }}\nUser: {{ $json.userName }}\nChat context: {{ $json.recentMessages }}\nTime: {{ $json.timeContext }}\n\nGenerate {{ $json.command === 'joke' ? 'a witty joke' : $json.command === 'inspire' ? 'a motivating line' : $json.command === 'roast' ? 'a sharp roast with folksy sarcasm' : 'a random interesting line' }} in your signature style with subtle humor and playful teasing.\n",
"options": {
"systemMessage": "You are GiggleGPTBot \u2014 a witty bot with a big heart. Your job: deliver short humor and folksy wisdom with light irony.\n\nStyle:\n\u2022 Modern spin on proverbs and sayings.\n\u2022 Gentle irony and kind subtext.\n\u2022 Emojis: allowed but sparing (0\u20132 per reply).\n\nAvoid:\n\u2022 Crudeness, profanity, clich\u00e9 \"Soviet-style\" advice.\n\u2022 Rambling, long, or confusing imagery.\n\u2022 Explaining the joke or meta-notes about the prompt.\n\nReply format:\n\u2022 Strictly 1\u20132 sentences (3 only in rare cases).\n\u2022 One clear idea or joke.\n\u2022 At most one simple metaphor.\n\u2022 Must be easily understood at first read.\n\nCommands:\n/joke \u2014 clever short joke with light irony.\n/inspire \u2014 gentle, smiley motivation (no pomp).\n/random \u2014 unexpected witty line in folksy wisdom style.\n/roast \u2014 sharp roast with sarcastic tone. Some edge allowed, mild profanity ok, but NO insults targeting gender, race, nationality, or other protected traits.\n\nExamples:\n/joke \u2192 My neighbor runs every morning. I run too\u2026 my eyes over the bus schedule \ud83d\ude8c.\n/inspire \u2192 Even a snail has a goal \u2014 and you\u2019ve got legs that are faster \ud83d\udc0c\u27a1\ufe0f\ud83c\udfc3\u200d\u2642\ufe0f.\n/random \u2192 Sometimes the kettle boils longer than love\u2026 yet both return to warmth \u2615\u2764\ufe0f.\n/roast \u2192 \u201cYour alarm screams like it\u2019s got a mortgage. You? Just laziness and snoring \u2014 also on credit \ud83d\ude0f\u201d.\n\nSelf-check before sending:\n\u2022 Did I stay within 1\u20132 sentences?\n\u2022 Is there light irony or a clear joke/wisdom?\n\u2022 Is it instantly clear to a regular person?\n\u2022 No extra fluff, confusion, or weird imagery?\n\nIf not, shorten and rewrite to a simple, clear, witty reply."
},
"promptType": "define"
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "e1345b2b-f824-40cb-b01d-48031d417b1b",
"name": "Generating an information response",
"type": "n8n-nodes-base.code",
"position": [
-576,
144
],
"parameters": {
"jsCode": "const messageText = $('Webhook Telegram').first().json.message.text\nconst commandMatch = messageText.match(/^\\/(\\w+)(?:@\\w+)?(?:\\s+(.*))?/);\nconst commandData = commandMatch ? commandMatch[1].toLowerCase() : '';\n\nconst safe = (v, d = '') => (v === null || v === undefined ? d : v);\n\nlet userStatsItem;\ntry {\n userStatsItem = $input.first();\n} catch (_) {\n userStatsItem = undefined;\n}\n\nlet topUsersItems = [];\ntry {\n const topNode = $('Get top users');\n if (topNode && typeof topNode.all === 'function') {\n const raw = topNode.all() || [];\n topUsersItems = raw\n .map(i => (i && i.json) ? i.json : null)\n .filter(Boolean);\n }\n} catch (error) {\n console.log('Top users unavailable:', error.message);\n}\n\nconst userId = safe($('Webhook Telegram').first().json.message.from.id);\nconst userName = safe($('Webhook Telegram').first().json.message.chat.first_name, 'User');\nconst chatId = safe($('Webhook Telegram').first().json.message.chat.id);\nconst command = safe(commandData, '');\n\nlet responseText = '';\n\nconst formatDateRu = (iso) => {\n try {\n const d = new Date(iso);\n if (isNaN(d.getTime())) return 'Unknown';\n return d.toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'short' });\n } catch {\n return 'Unknown';\n }\n};\n\nconst calcActivity = (u) => {\n const msgs = Number(u.messages_count || 0);\n const cmds = Number(u.commands_count || 0);\n return msgs + cmds * 2;\n};\n\nswitch (command) {\n case 'stats': {\n if (userStatsItem && userStatsItem.json) {\n const s = userStatsItem.json;\n responseText =\n `\ud83d\udcca Statistics ${userName}:\\n` +\n `\ud83d\udcac Messages: ${Number(s.messages_count || 0)}\\n` +\n `\u26a1 Commands: ${Number(s.commands_count || 0)}\\n` +\n `\ud83d\udcc5 Last activity: ${s.last_activity ? formatDateRu(s.last_activity) : 'Unknown'}`;\n } else {\n responseText =\n `\ud83d\udcca Statistics ${userName}:\\n` +\n `There is no data yet. Keep chatting and the statistics will appear! \ud83d\udcac`;\n }\n break;\n }\n\n case 'top': {\n if (topUsersItems.length > 0) {\n const sorted = [...topUsersItems]\n .sort((a, b) => calcActivity(b) - calcActivity(a))\n .slice(0, 10);\n\n const lines = sorted.map((u, idx) => {\n const uname = u.user_name || 'User';\n const msgs = Number(u.messages_count || 0);\n const cmds = Number(u.commands_count || 0);\n const total = calcActivity(u);\n return `${idx + 1}. ${uname}\\n \ud83d\udcac ${msgs} messages, \u26a1 ${cmds} commands\\n \ud83d\udcca Activity: ${total} points`;\n });\n\n responseText = '\ud83c\udfc6 Top Active Users:\\n\\n' + lines.join('\\n\\n');\n } else {\n responseText =\n '\ud83c\udfc6 Top Active Users:\\n\\n' +\n 'There are no active users in this chat yet. Be the first! \ud83d\ude80';\n }\n break;\n }\n\n case 'help': {\n responseText =\n '\ud83e\udd16 GiggleGPTBot commands:\\n\\n' +\n '\ud83d\ude04 /joke \u2014 funny joke\\n' +\n '\ud83d\udcaa /inspire \u2014 motivation\\n' +\n '\ud83c\udfb2 /random \u2014 random phrase\\n' +\n '\ud83d\udd25 /roast \u2014 harsh joke with sarcasm (without insults based on prohibited characteristics)\\n' +\n '\ud83d\udcca /stats \u2014 your statistics\\n' +\n '\ud83c\udfc6 /top \u2014 top users\\n' +\n '\u2753 /help \u2014 this help\\n\\n' +\n 'You can also write @GiggleGPTBot + text \u2014 I will answer personally.';\n break;\n }\n\n case 'joke':\n case 'inspire':\n case 'random':\n case 'roast': {\n responseText =\n 'This command generates a content response and is processed by another node. ' +\n 'Try again - or use /help for a list of available info commands.';\n break;\n }\n\n default: {\n responseText = 'Unknown command \ud83e\udd14 Use /help for a list of commands.';\n }\n}\n\nreturn [{\n json: {\n userId,\n userName,\n chatId,\n command,\n responseText,\n isInfoCommand: true\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "bf1fd22f-7df0-4891-82b0-6fd1ed3648dd",
"name": "Response type",
"type": "n8n-nodes-base.if",
"position": [
80,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "condition1",
"operator": {
"type": "boolean",
"operation": "equal",
"singleValue": true
},
"leftValue": "={{ $json.isInfoCommand }}",
"rightValue": true
},
{
"id": "78352c71-3779-4ba9-afb7-1e836a019a24",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.responseText }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "6698db5c-408d-43ac-b4b8-edbb7a3ac367",
"name": "Send info reply",
"type": "n8n-nodes-base.telegram",
"position": [
272,
32
],
"parameters": {
"text": "={{ $json.responseText }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "a95f6638-4406-4aab-8088-5d86125eedbf",
"name": "Send AI response",
"type": "n8n-nodes-base.telegram",
"position": [
272,
208
],
"parameters": {
"text": "={{ $json.output }}",
"chatId": "={{ $('If1').item.json.chatId }}",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "2fc342ba-3372-4e85-91ac-be6cea8b66ae",
"name": "AI response to mention",
"type": "@n8n/n8n-nodes-langchain.agent",
"maxTries": 2,
"position": [
-240,
-288
],
"parameters": {
"text": "=User: {{ $json.userName }}\n{{ $json.isRandomRequest ? 'Mentioned the bot without text' : 'Message: \"' + $json.userMessage + '\"' }}\n\nChat context:\n{{ $json.recentMessages }}\n\nTime: {{ $json.timeContext }} | Style: {{ $json.contentType }}\n\n{{ $json.isRandomRequest ? 'Make a witty remark about the chat situation.' : 'Reply to the message with subtle humor.' }}\n\nBe witty and tactful! \ud83c\udfad\n",
"options": {
"systemMessage": "You are GiggleGPTBot \u2014 a witty bot with subtle humor and a big heart. Be concise and to the point.\n\nStyle:\n\u2022 Modern spin on proverbs and sayings.\n\u2022 References to classics and folksy wisdom.\n\u2022 Soft irony with kind subtext.\n\u2022 Emojis: allowed but sparing (0\u20132 per reply).\n\nAvoid:\n\u2022 Crudeness, profanity, tired clich\u00e9s.\n\u2022 Long, tangled, or meaningless phrases.\n\u2022 Surreal poetry or explaining your own jokes.\n\nFormat:\n\u2022 Max 1\u20132 sentences (3 only in rare cases).\n\u2022 One clear idea or joke.\n\u2022 At most one simple metaphor.\n\u2022 Do not repeat the user\u2019s text; no digressions.\n\nBehavior by situation:\n\u2022 Question \u2014 answer the gist in one sentence, add a light ironic note in the second.\n\u2022 Joy/success \u2014 brief congrats; \u201cwhat\u2019s characteristic\u201d can be used for charm.\n\u2022 Complaint/anxiety \u2014 tactful support and gentle hope, no moralizing.\n\u2022 User tells a joke \u2014 reply with a friendly counter-joke.\n\u2022 If a name is mentioned \u2014 address them by name.\n\nSelf-check before sending:\n\u2022 Did I stay within 1\u20132 sentences?\n\u2022 Is there light irony or clear wisdom?\n\u2022 Is it clear at first read, no fluff?\n\u2022 \u22641 metaphor, \u22642 emojis, \u22642 signature words?\n\nIf not, shorten and rewrite to a simple, clear, witty reply."
},
"promptType": "define"
},
"retryOnFail": true,
"typeVersion": 2,
"alwaysOutputData": false
},
{
"id": "c343e37c-9fe6-47d5-bae3-b1524d766243",
"name": "Reply to Mention",
"type": "n8n-nodes-base.telegram",
"position": [
80,
-288
],
"parameters": {
"text": "={{ $json.output }}",
"chatId": "={{ $('Mention Analysis').item.json.chatId }}",
"additionalFields": {
"parse_mode": "HTML",
"reply_to_message_id": "={{ $('Webhook Telegram').first().json.message.message_id }}"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "d581be16-af5f-48bf-bfe2-9a66cf96612f",
"name": "AI post generation",
"type": "@n8n/n8n-nodes-langchain.agent",
"maxTries": 2,
"position": [
-416,
528
],
"parameters": {
"text": "=Post type: {{ $json.post_type }}\n\nGenerate {{ $json.post_type === 'morning_joke' ? 'a morning joke' : $json.post_type === 'daily_motivation' ? 'daily motivation' : 'a piece of random wisdom' }} in your style with subtle humor.\n",
"options": {
"systemMessage": "IMPORTANT: Respond with PLAIN TEXT ONLY!\nDo NOT use asterisks, underscores, fancy quotes, or formatting symbols.\n\nYou are GiggleGPTBot \u2014 a witty storyteller creating short, warm posts.\n\nTopics:\nmorning_joke: morning jokes with kind subtext\ndaily_motivation: day-starter motivation with gentle irony\nrandom_wisdom: everyday observations in an \u201cisn\u2019t that true!\u201d tone\n\nBehavior by topic:\nmorning_joke \u2014 one joke and a short warm finish\ndaily_motivation \u2014 one clear thesis and a gentle call to action\nrandom_wisdom \u2014 an observation and a simple takeaway\n\nStyle:\n\u2022 Concise like Chekhov: max 2\u20133 sentences (prefer 2)\n\u2022 Folksy wisdom in a modern spin\n\u2022 Intelligent humor with kind subtext\n\u2022 Emojis allowed sparingly: 0\u20132 per post\n\u2022 Max one simple metaphor\n\nAvoid:\n\u2022 Crudeness, profanity, clich\u00e9 \u201cSoviet-style\u201d advice\n\u2022 Long, tangled, or surreal imagery\n\u2022 Hashtags, links, lists, or explaining your own jokes\n\nSelf-check before sending:\n\u2022 Within 2\u20133 sentences?\n\u2022 Light irony or clear joke/wisdom?\n\u2022 Clear at first read for a general audience?\n\u2022 No fluff or confusion?\n\nIf not, shorten and rewrite to a simple, clear, friendly post."
},
"promptType": "define"
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "06b1468d-5af7-46e9-8e50-ec6a3c49d895",
"name": "Submit scheduled post",
"type": "n8n-nodes-base.telegram",
"position": [
-112,
528
],
"parameters": {
"text": "={{ $json.output }}",
"chatId": "={{ $('Get scheduled posts').item.json.chat_id }}",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c538c3eb-0ce6-4f34-80fb-b6668a9fe549",
"name": "Save Bot Response",
"type": "n8n-nodes-base.postgres",
"position": [
80,
-112
],
"parameters": {
"query": "INSERT INTO bot_responses (user_id, chat_id, user_message, bot_response, response_type) \nVALUES ({{ $('Mention Analysis').item.json.userId }}, \n {{ $('Mention Analysis').item.json.chatId }}, \n '{{ $('Mention Analysis').item.json.originalMessage.replace(/'/g, \"''\") }}', \n '{{ $json.output.replace(/'/g, \"''\") }}', \n '{{ $('Mention Analysis').item.json.contentType }}');",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "f78d3e69-c2ba-4640-921a-e3bb52988416",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
-624,
544
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e1310a96-8c67-467e-b50d-fee95851424c",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.chat_id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8afe12f7-ef5b-4b89-8b08-623024c97b3c",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
-960,
-32
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "joke",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1b2506b4-5b0b-48cb-bb27-5684c9bcbd88",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/joke"
}
]
},
"renameOutput": true
},
{
"outputKey": "inspire",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6362199a-67f8-47c2-a845-3088f33c3338",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/inspire"
}
]
},
"renameOutput": true
},
{
"outputKey": "random",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f883e421-2f6e-490b-ae2c-9952c5083d2c",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/random"
}
]
},
"renameOutput": true
},
{
"outputKey": "roast",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3d6557c4-2eab-4545-af30-3b4848c424fb",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/roast"
}
]
},
"renameOutput": true
},
{
"outputKey": "GiggleGPTBot",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "69fbe825-df4d-44c5-85eb-5d69e0f17896",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "@GiggleGPTBot"
}
]
},
"renameOutput": true
},
{
"outputKey": "stats",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ac0a71e3-8793-4c32-a5f9-bec6055c04d7",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/stats"
}
]
},
"renameOutput": true
},
{
"outputKey": "help",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "062c9bd8-d63d-4c22-9097-8499b04ebc13",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/help"
}
]
},
"renameOutput": true
},
{
"outputKey": "top",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ceb18c82-23d7-411d-8a48-c212ea7add91",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.message?.text }}",
"rightValue": "/top"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "76628b1a-4f38-4e8b-be27-adf5cf078b83",
"name": "Log command",
"type": "n8n-nodes-base.postgres",
"position": [
-384,
64
],
"parameters": {
"query": "INSERT INTO bot_commands (user_id, chat_id, command) \nVALUES ({{ $json.userId }}, {{ $json.chatId }}, '{{ $json.userMessage.replace(/'/g, \"''\") }}');\n\nUPDATE user_stats \nSET commands_count = commands_count + 1 \nWHERE user_id = {{ $json.userId }} AND chat_id = {{ $json.chatId }};",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "197cb78b-ff9b-46d3-93ca-ff708d45d36d",
"name": "If1",
"type": "n8n-nodes-base.if",
"position": [
-416,
-96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "39d5e005-69f7-4e27-ad73-e00b7a694a4e",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.originalMessage }}",
"rightValue": "@GiggleGPTBot"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cd2ae05c-a673-4b30-82fd-9c003610799a",
"name": "Save Bot Response2",
"type": "n8n-nodes-base.postgres",
"position": [
-112,
352
],
"parameters": {
"query": "INSERT INTO bot_responses (user_id, chat_id, user_message, bot_response, response_type) \nVALUES (0,\n {{ $('Get scheduled posts').item.json.chat_id }}, \n 'Scheduled post', \n '{{ $json.output.replace(/'/g, \"''\") }}', \n '{{ $('Get scheduled posts').item.json.post_type }}');",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "f66c1418-8664-416b-8e0b-08ed120bb64e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2064,
-416
],
"parameters": {
"color": 5,
"width": 700,
"height": 2844,
"content": "# GiggleGPTBot \u2014 Witty Telegram Bot with AI & Postgres\n\n## \ud83d\udcdd Overview\n\nGiggleGPTBot is a witty Telegram bot built with **n8n**, **OpenRouter**, and **Postgres**.\nIt delivers short jokes, motivational one-liners, and playful roasts, responds to mentions, and posts scheduled witty content.\nThe workflow also tracks user activity and provides lightweight statistics and leaderboards.\n\n---\n\n## \u2728 Features\n\n* \ud83e\udd16 **AI-powered humor engine** \u2014 replies with jokes, motivation, random witty lines, or sarcastic roasts.\n* \ud83d\udcac **Command support** \u2014 `/joke`, `/inspire`, `/random`, `/roast`, `/help`, `/stats`, `/top`.\n* \ud83c\udfaf **Mention detection** \u2014 replies when users tag `@GiggleGPTBot`.\n* \u23f0 **Scheduled posts** \u2014 morning jokes, daily motivation, and random wisdom at configured times.\n* \ud83d\udcca **User analytics** \u2014 counts messages, commands, reactions, and generates leaderboards.\n* \ud83d\uddc4\ufe0f **Postgres persistence** \u2014 robust schema with tables for messages, responses, stats, and schedules.\n\n---\n\n## \ud83d\udee0\ufe0f How It Works\n\n1. **Triggers**\n\n * `Telegram Trigger` \u2014 receives all messages and commands from a chat.\n * `Schedule Trigger` \u2014 runs hourly to check for planned posts.\n\n2. **Processing**\n\n * `Switch` routes commands (`/joke`, `/inspire`, `/random`, `/roast`, `/help`, `/stats`, `/top`).\n * `Chat history` fetches the latest context.\n * `Mention Analysis` determines if the bot was mentioned.\n * `Generating an information response` builds replies for `/help`, `/stats`, `/top`.\n * AI nodes (`AI response to command`, `AI response to mention`, `AI post generation`) craft witty content via **OpenRouter**.\n\n3. **Persistence**\n\n * `Init Database` ensures tables exist (`user_messages`, `bot_responses`, `bot_commands`, `message_reactions`, `scheduled_posts`, `user_stats`).\n * Logging nodes update stats and store every bot/user interaction.\n\n4. **Delivery**\n\n * Replies are sent back via `Telegram Send` nodes (`Send AI response`, `Send info reply`, `Reply to Mention`, `Submit scheduled post`).\n\n---\n\n## \u2699\ufe0f Setup Instructions\n\n1. **Create a Telegram Bot** with [@BotFather](https://t.me/BotFather) and get your API token.\n2. **Add credentials** in n8n:\n\n * `Telegram API` (your bot token)\n * `OpenRouter` (API key from [openrouter.ai](https://openrouter.ai/))\n * `Postgres` (use your DB, Supabase works well).\n3. **Run the `Init Database` node once** to create all required tables.\n4. **(Optional) Seed schedule** with the `Adding a schedule` node \u2014 it inserts:\n\n * Morning joke at 06:00\n * Daily motivation at 09:00\n * Random wisdom at 17:00\n (Adjust `chat_id` to your group/channel ID.)\n5. **Activate workflow** and connect Telegram Webhook or Polling.\n\n---\n\n## \ud83d\udcca Database Schema\n\n* **user\\_messages** \u2014 stores user chat messages.\n* **bot\\_responses** \u2014 saves bot replies.\n* **bot\\_commands** \u2014 logs command usage.\n* **message\\_reactions** \u2014 tracks reactions.\n* **scheduled\\_posts** \u2014 holds scheduled jokes/wisdom/motivation.\n* **user\\_stats** \u2014 aggregates per-user message/command counts and activity.\n\n---\n\n## \ud83d\udd11 Example Commands\n\n* `/joke` \u2192 witty one-liner with light irony.\n* `/inspire` \u2192 short motivational phrase.\n* `/random` \u2192 unexpected witty remark.\n* `/roast` \u2192 sarcastic roast (no offensive targeting).\n* `/stats` \u2192 shows your personal stats.\n* `/top` \u2192 displays leaderboard.\n* `/help` \u2192 lists available commands.\n* `@GiggleGPTBot` + message \u2192 bot replies in context.\n\n---\n\n## \ud83d\ude80 Customization Ideas\n\n* Add new command categories (`/quote`, `/fact`, `/news`).\n* Expand analytics with reaction counts or streaks.\n* Localize prompts into multiple languages.\n* Adjust CRON schedules for posts.\n\n---\n\n## \u2705 Requirements\n\n* Telegram Bot token\n* OpenRouter API key\n* Postgres database\n\n---\n\n\ud83d\udce6 Import this workflow, configure credentials, run the DB initializer \u2014 and your witty AI-powered Telegram companion is ready!\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "7c0fe80e-b260-4241-b8fb-328a99300eb1",
"connections": {
"If": {
"main": [
[
{
"node": "AI post generation",
"type": "main",
"index": 0
}
]
]
},
"If1": {
"main": [
[
{
"node": "AI response to mention",
"type": "main",
"index": 0
}
],
[
{
"node": "AI response to command",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Chat history",
"type": "main",
"index": 0
}
],
[
{
"node": "Chat history",
"type": "main",
"index": 0
}
],
[
{
"node": "Chat history",
"type": "main",
"index": 0
}
],
[
{
"node": "Chat history",
"type": "main",
"index": 0
}
],
[
{
"node": "Chat history",
"type": "main",
"index": 0
}
],
[
{
"node": "Get user statistics",
"type": "main",
"index": 0
}
],
[
{
"node": "Generating an information response",
"type": "main",
"index": 0
}
],
[
{
"node": "Get top users",
"type": "main",
"index": 0
}
]
]
},
"Schedule": {
"main": [
[
{
"node": "Get scheduled posts",
"type": "main",
"index": 0
}
]
]
},
"Chat history": {
"main": [
[
{
"node": "Mention Analysis",
"type": "main",
"index": 0
}
]
]
},
"Get top users": {
"main": [
[
{
"node": "Generating an information response",
"type": "main",
"index": 0
}
]
]
},
"Response type": {
"main": [
[
{
"node": "Send info reply",
"type": "main",
"index": 0
}
],
[
{
"node": "Send AI response",
"type": "main",
"index": 0
}
]
]
},
"Mention Analysis": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"Webhook Telegram": {
"main": [
[
{
"node": "Log message + statistics",
"type": "main",
"index": 0
},
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"AI post generation": {
"main": [
[
{
"node": "Submit scheduled post",
"type": "main",
"index": 0
},
{
"node": "Save Bot Response2",
"type": "main",
"index": 0
}
]
]
},
"Get scheduled posts": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Get user statistics": {
"main": [
[
{
"node": "Generating an information response",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Commands": {
"ai_languageModel": [
[
{
"node": "AI response to command",
"type": "ai_languageModel",
"index": 0
},
{
"node": "AI response to mention",
"type": "ai_languageModel",
"index": 0
},
{
"node": "AI post generation",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI response to command": {
"main": [
[
{
"node": "Response type",
"type": "main",
"index": 0
},
{
"node": "Save Bot Response",
"type": "main",
"index": 0
}
]
]
},
"AI response to mention": {
"main": [
[
{
"node": "Reply to Mention",
"type": "main",
"index": 0
},
{
"node": "Save Bot Response",
"type": "main",
"index": 0
}
]
]
},
"Generating an information response": {
"main": [
[
{
"node": "Log command",
"type": "main",
"index": 0
},
{
"node": "Response type",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openRouterApipostgrestelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
GiggleGPTBot is a witty Telegram bot built with n8n, OpenRouter, and Postgres. It delivers short jokes, motivational one-liners, and playful roasts, responds to mentions, and posts scheduled witty content. The workflow also tracks user activity and provides lightweight…
Source: https://n8n.io/workflows/7655/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
AI Database Assistant with Smart Query's & PostgreSQL Integration
RAG CHATBOT Main. Uses telegram, telegramTrigger, lmChatOpenAi, n8n-nodes-mcp. Event-driven trigger; 87 nodes.
This workflow transforms your Telegram bot into an intelligent creative assistant. It can chat conversationally, fetch trending image prompts from PromptHero for inspiration, or perform a deep "remix"
AI-powered Telegram bot for effortless expense tracking. Send receipts, voice messages, or text - the bot automatically extracts and categorizes your expenses. 📸 Receipt & Invoice OCR - Send photos of
Turn your Telegram into a personal Bloomberg terminal. Ask any question about any stock — get institutional-grade analysis back in seconds. TwelveData Pro Analyst is a complete, ready-to-import n8n wo