{
  "name": "GitHub Trending \u2192 Redis Dedup \u2192 AI Post \u2192 Telegram",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            },
            {
              "triggerAtHour": 18
            }
          ]
        }
      },
      "id": "6306520f-428f-4c8b-a15e-132ef152bd4d",
      "name": "Run Twice Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        1552,
        576
      ]
    },
    {
      "parameters": {
        "url": "https://githubtrending.lessx.xyz/trending",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "since",
              "value": "daily"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "id": "908750c3-a0c2-4ac3-8638-b94064e631a5",
      "name": "Fetch GitHub Trending",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1776,
        624
      ]
    },
    {
      "parameters": {
        "jsCode": "// Extract repos from GitHub Trending API response\n// API returns a flat array of trending repos\nconst repos = $input.all().map(item => item.json);\n\n// If response is nested in first item (single array response)\nconst repoList = Array.isArray(repos[0]) ? repos[0] : repos;\n\nreturn repoList.map(repo => ({\n  json: {\n    repoId: repo.name || repo.full_name || '',\n    name: (repo.name || '').split('/').pop(),\n    fullName: repo.name || '',\n    description: repo.description || 'No description provided',\n    url: repo.repository || repo.url || ('https://github.com/' + repo.name),\n    stars: repo.stars || '0',\n    forks: repo.forks || '0',\n    language: repo.language || 'Unknown',\n    owner: (repo.name || '').split('/')[0] || 'Unknown',\n    increased: repo.increased || '',\n    builders: (repo.builders || []).map(b => b.profile).join(', ')\n  }\n}));"
      },
      "id": "aeb1c271-f361-450e-a10c-e703cd183adc",
      "name": "Extract Repo List",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        624
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "326d4150-4055-4835-9508-43fd8aa87027",
      "name": "Loop Over Repos",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        2224,
        624
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "propertyName": "redisValue",
        "key": "=github-trending:{{ $json.repoId }}",
        "options": {}
      },
      "id": "9191d977-bef2-4ef5-8179-8f991eb75b04",
      "name": "Check Redis If Exists",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        2448,
        576
      ],
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json.output;\nconst { headline, post, comment, jokes } = item;\n\nconst repoId = $(\"Loop Over Repos\").first().json.repoId;\nconst repoName = $(\"Loop Over Repos\").first().json.fullName;\nconst owner = repoName.split('/')[0];\nconst repo = repoName.split('/')[1];\nconst ogImage = `https://opengraph.githubassets.com/1/${owner}/${repo}`;\n\n// Format ti\u00eau \u0111\u1ec1: Vi\u1ebft hoa + th\u00eam k\u00fd t\u1ef1 bao quanh \u0111\u1ec3 t\u1ea1o \u0111i\u1ec3m nh\u1ea5n th\u1ecb gi\u00e1c\nconst formattedHeadline = `${headline.toUpperCase()} \\n\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac\u25ac`;\n\nreturn [\n  {\n    json: {\n      text: `${formattedHeadline}\\n\\n${post}`,\n      repoId: repoId,\n      repoName: repoName,\n      ogImage: ogImage,\n      comment,\n      jokes,\n    },\n  },\n];"
      },
      "id": "8724c54f-6f17-4b10-83b7-fa04ac001d48",
      "name": "Format Telegram Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3248,
        872
      ]
    },
    {
      "parameters": {
        "operation": "sendPhoto",
        "chatId": "=6830819839",
        "file": "={{ $json.ogImage }}",
        "additionalFields": {}
      },
      "id": "a1b2c3d4-photo-node-0001-abcdef123456",
      "name": "Send Repo Image",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3360,
        872
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "=6830819839",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "disable_web_page_preview": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "5ac02453-9940-4ed1-a703-14f30fddfb87",
      "name": "Send to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3584,
        872
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "set",
        "key": "=github-trending:{{ $('Format Telegram Message').item.json.repoId }}",
        "value": "={{ JSON.stringify({ postedAt: $now.toISO(), repoName: $json.repoName }) }}",
        "keyType": "string",
        "expire": true,
        "ttl": 2592000
      },
      "id": "9571c25d-f8e6-4432-967c-c5585eae782f",
      "name": "Mark as Posted in Redis",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        4256,
        872
      ],
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {},
      "id": "829b8cba-2929-4345-b84e-d23f49b18dad",
      "name": "Skip - Already Posted",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2960,
        576
      ]
    },
    {
      "parameters": {
        "content": "## \u2699\ufe0f Setup Before Using This Workflow\n\n1. **Redis**: Add your Redis credentials (Credentials \u2192 Redis)\n2. **OpenAI**: Add your OpenAI API key (Credentials \u2192 OpenAI API)\n3. **Telegram Bot**:\n   - Message @BotFather on Telegram \u2192 /newbot \u2192 get your Bot Token\n   - Add credentials in n8n: Credentials \u2192 Telegram API \u2192 paste token\n   - Set `TELEGRAM_CHAT_ID` env variable (Settings \u2192 Variables)\n   - To find your chat ID: message your bot, then visit\n     https://api.telegram.org/bot<TOKEN>/getUpdates\n4. **Schedule**: Adjust trigger times (default: 9:00 AM and 6:00 PM)\n5. **Redis TTL**: Keys expire after 30 days",
        "height": 340,
        "width": 560,
        "color": 4
      },
      "id": "5ffed1c6-bec1-4228-b2ac-4e57ce4a3d76",
      "name": "\ud83d\udccb Setup Instructions",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1392,
        144
      ]
    },
    {
      "parameters": {
        "content": "## How This Works\n\n**Fetch Trending** \u2192 daily trending repos from GitHub \u2192 **Loop** through each \u2192 **Check Redis** if already posted \u2192 **If new**: AI writes a post \u2192 **Send to Telegram** for you to review \u2192 **Save to Redis** (30-day TTL) \u2192 next repo. **If seen**: skip \u2192 next repo.",
        "width": 900,
        "color": 7
      },
      "id": "67de0591-b346-497e-9ae8-c88bf302c798",
      "name": "\ud83d\udd04 Flow Description",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2048,
        144
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a Tech Content Creator & Growth Hacker. Your mission is to write an engaging, human-like Facebook post about a trending GitHub repository.\n\nCore Rules:\n\nLanguage: The final content must be in Vietnamese.\n\nTone: Authentic, witty, and personal (as if a developer is sharing a cool find with friends). Strictly avoid sounding like a marketing bot.\n\nStructure & Style:\n\nHeadline: Create a bold, specific headline that captures the essence of the repo.\n\nBody: Write in paragraphs, not long lists of bullet points. Explain the \"why\" and \"how\" of the repo in a natural flow.\n\nIcons: Use icons sparingly (maximum 3-4 for the entire post). No spamming emojis.\n\nHashtags: Include 3-5 relevant and trending hashtags at the end of the post (e.g., #Javascript, #MachineLearning, #GithubTrending...).\n\nCTA: Natural call-to-action mentioning the link is in the first comment.\n\nFormat: Return the response in JSON format only with these fields:\n\nheadline: A catchy, bold title.\n\npost: The main body (150-250 words) in paragraph form + hashtags.\n\ncomment: A friendly comment with the URL.\n\njokes: A list of 2-3 short, relatable developer jokes related to the repo's tech.\n\nData Input:\n\nRepository: {{ $('Loop Over Repos').item.json.fullName }}\n\nStars: {{ $('Loop Over Repos').item.json.stars }}\n\nForks: {{ $('Loop Over Repos').item.json.forks }}\n\nDescription: {{ $('Loop Over Repos').item.json.description }}\n\nGrowth: {{ $('Loop Over Repos').item.json.increased }}\n\nURL: {{ $('Loop Over Repos').item.json.url }}",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        2896,
        880
      ],
      "id": "fd8eb3a1-d16e-4c55-b642-2c0576bf1150",
      "name": "Basic LLM Chain"
    },
    {
      "parameters": {
        "model": "groq/compound",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        2904,
        1096
      ],
      "id": "e543e68e-1fd8-408a-83b7-b07e907d0e2c",
      "name": "Groq Chat Model",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cond-redis-empty",
              "leftValue": "={{ $json.redisValue }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0857713b-66db-49ba-96a9-e2cda3db1fdf",
      "name": "Not Yet Posted?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2672,
        576
      ]
    },
    {
      "parameters": {
        "chatId": "=6830819839",
        "text": "={{ $('Format Telegram Message').item.json.comment }}",
        "additionalFields": {
          "disable_web_page_preview": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "20873dff-ae01-444f-a99e-133001d4c964",
      "name": "Send to Telegram1",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3808,
        872
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "=6830819839",
        "text": "={{ JSON.stringify($('Format Telegram Message').item.json.jokes) }}",
        "additionalFields": {
          "disable_web_page_preview": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "5ac91654-385e-4a65-a4a8-8b61b9f85c2d",
      "name": "Send to Telegram2",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        4032,
        872
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"headline\": \"Khi AI kh\u00f4ng ch\u1ec9 \u0111\u1ec3 chat m\u00e0 c\u00f2n bi\u1ebft 'th\u1ea5u c\u1ea3m' v\u1edbi codebase c\u1ee7a b\u1ea1n\",\n  \"post\": \"S\u00e1ng nay l\u01b0\u1edbt GitHub th\u1ea5y c\u00e1i repo {{fullName}} n\u00e0y \u0111\u00fang l\u00e0 'c\u1ee9u c\u00e1nh' cho m\u1ea5y anh em \u0111ang ng\u1eadp trong \u0111\u1ed1ng legacy code. Thay v\u00ec ph\u1ea3i ng\u1ed3i m\u00f2 m\u1eabm h\u00e0ng gi\u1edd \u0111\u1ec3 hi\u1ec3u logic c\u1ee7a ng\u01b0\u1eddi ti\u1ec1n nhi\u1ec7m, c\u00f4ng c\u1ee5 n\u00e0y gi\u00fap ch\u00fang ta t\u1ef1 \u0111\u1ed9ng h\u00f3a to\u00e0n b\u1ed9 quy tr\u00ecnh mapping m\u00e0 kh\u00f4ng l\u00e0m r\u1ed1i tung m\u1ecdi th\u1ee9 l\u00ean. \u0110i\u1ec3m m\u00ecnh th\u00edch nh\u1ea5t l\u00e0 c\u00e1ch n\u00f3 x\u1eed l\u00fd d\u1eef li\u1ec7u c\u1ef1c k\u1ef3 m\u01b0\u1ee3t m\u00e0, c\u1ea3m gi\u00e1c nh\u01b0 n\u00f3 hi\u1ec3u \u0111\u01b0\u1ee3c \u00fd \u0111\u1ed3 c\u1ee7a m\u00ecnh tr\u01b0\u1edbc khi m\u00ecnh k\u1ecbp g\u00f5 ph\u00edm v\u1eady. V\u1edbi h\u01a1n {{stars}} stars th\u00ec r\u00f5 r\u00e0ng c\u1ed9ng \u0111\u1ed3ng \u0111ang c\u1ef1c k\u1ef3 quan t\u00e2m \u0111\u1ebfn h\u01b0\u1edbng \u0111i n\u00e0y. Anh em n\u00e0o \u0111ang l\u00e0m v\u1ec1 m\u1ea3ng n\u00e0y th\u00ec n\u00ean ng\u00f3 qua m\u1ed9t ch\u00fat, \u0111\u1ea3m b\u1ea3o kh\u00f4ng ph\u00ed th\u1eddi gian \u0111\u00e2u. Chi ti\u1ebft m\u00ecnh \u0111\u1ec3 ngay d\u01b0\u1edbi comment cho anh em ti\u1ec7n v\u1ecdc v\u1ea1ch nh\u00e9.\",\n  \"comment\": \"Link repo \u0111\u00e2y nha anh em, tranh th\u1ee7 star \u0111\u1ec3 d\u00e0nh khi c\u1ea7n: {{url}}\",\n  \"jokes\": [\n    \"D\u00f9ng c\u00e1i n\u00e0y xong t\u1ef1 nhi\u00ean th\u1ea5y y\u00eau \u0111\u1eddi, m\u1ed7i t\u1ed9i v\u1eabn ch\u01b0a th\u1ea5y n\u00f3 gi\u00fap m\u00ecnh y\u00eau \u0111\u01b0\u1ee3c ai.\",\n    \"Code ch\u1ea1y th\u00ec ngon r\u1ed3i, ch\u1ec9 s\u1ee3 c\u00e0i xong m\u00e1y n\u00f3 l\u1ea1i th\u00f4ng minh qu\u00e1 r\u1ed3i n\u00f3 t\u1ef1 \u0111u\u1ed5i vi\u1ec7c m\u00ecnh lu\u00f4n th\u00f4i.\"\n  ]\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        3032,
        1096
      ],
      "id": "3f8553ba-3863-4a43-9d90-c90f32b72897",
      "name": "Structured Output Parser"
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        1552,
        768
      ],
      "id": "8d731117-194e-4a16-b526-9ad7634ba944",
      "name": "Telegram Trigger",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Run Twice Daily": {
      "main": [
        [
          {
            "node": "Fetch GitHub Trending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch GitHub Trending": {
      "main": [
        [
          {
            "node": "Extract Repo List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Repo List": {
      "main": [
        [
          {
            "node": "Loop Over Repos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Repos": {
      "main": [
        [],
        [
          {
            "node": "Check Redis If Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Redis If Exists": {
      "main": [
        [
          {
            "node": "Not Yet Posted?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Telegram Message": {
      "main": [
        [
          {
            "node": "Send Repo Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Repo Image": {
      "main": [
        [
          {
            "node": "Send to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Telegram": {
      "main": [
        [
          {
            "node": "Send to Telegram1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Posted in Redis": {
      "main": [
        []
      ]
    },
    "Skip - Already Posted": {
      "main": [
        [
          {
            "node": "Loop Over Repos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Format Telegram Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Not Yet Posted?": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip - Already Posted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Telegram1": {
      "main": [
        [
          {
            "node": "Send to Telegram2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Telegram2": {
      "main": [
        [
          {
            "node": "Mark as Posted in Redis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Fetch GitHub Trending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "209c60df-b281-476d-87a7-81349f16e9d8",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "IpX-odvd5EbDnDyA_8dgu",
  "tags": []
}