GitHub-Fork-Repository-Statistiken

Experte

Dies ist ein Engineering-Bereich Automatisierungsworkflow mit 19 Nodes. Hauptsächlich werden If, Code, Merge, Filter, Github und andere Nodes verwendet. GitHub-Fork-Status-Monitor

Voraussetzungen
  • GitHub Personal Access Token
  • Telegram Bot Token
  • Möglicherweise sind Ziel-API-Anmeldedaten erforderlich

Kategorie

Workflow-Vorschau
Visualisierung der Node-Verbindungen, mit Zoom und Pan
Workflow exportieren
Kopieren Sie die folgende JSON-Konfiguration und importieren Sie sie in n8n
{
  "id": "BnXe0TTG2ep3jJvY",
  "meta": {
    "instanceId": "ade7753ab8e61ecd17667d748d0e70dc7e52ae3b447190f062e3dc18fc407d44",
    "templateCredsSetupCompleted": true
  },
  "name": "github-forked-repo-stats",
  "tags": [
    {
      "id": "ByjgsuAI1ZOmR1Cv",
      "name": "github",
      "createdAt": "2025-06-10T19:03:59.044Z",
      "updatedAt": "2025-06-10T19:03:59.044Z"
    }
  ],
  "nodes": [
    {
      "id": "d27cc197-fa61-421b-8422-8c4e5f716efe",
      "name": "Bei Klick auf 'Workflow ausführen'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        380,
        -260
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5d55b266-a8b8-450f-903c-b04f47a1d708",
      "name": "Über Elemente iterieren",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1160,
        -80
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "c160d1cf-67a0-4e5a-9a0d-f6c78ad18154",
      "name": "Repository-Details abrufen",
      "type": "n8n-nodes-base.github",
      "position": [
        1380,
        -80
      ],
      "webhookId": "2e042451-77f6-447f-b798-f4d8d2c43788",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.owner.login }}"
        },
        "resource": "repository",
        "operation": "get",
        "repository": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $json.html_url }}"
        }
      },
      "credentials": {
        "githubApi": {
          "id": "qdeIXe3PnQJwdyiy",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "7580ab52-4cfc-490f-9f3e-021bba782db4",
      "name": "Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1740,
        -480
      ],
      "webhookId": "62a8eb02-eb84-426e-92e2-bb8e65f00ad5",
      "parameters": {
        "text": "={{ $json.telegramMessage }}",
        "chatId": "6083752603",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "Apigm5Vud7RfQEmI",
          "name": "telegram-chintupintu-bot"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f0170087-277f-4f32-bc45-305dd56211df",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -40,
        -40
      ],
      "webhookId": "1dcc5ea0-8ab9-42a2-bdc0-06976ea88ed1",
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "Apigm5Vud7RfQEmI",
          "name": "telegram-chintupintu-bot"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f22d2c47-5d0a-4574-8293-e7ddc9a326ac",
      "name": "Upstream-URL vorbereiten",
      "type": "n8n-nodes-base.code",
      "position": [
        1600,
        -80
      ],
      "parameters": {
        "jsCode": "const items = [];\n\nconsole.log(`Code Node (Prepare Upstream URL): Received ${$input.all().length} input items.`);\n\nfor (const item of $input.all()) {\n  const forkRepo = item.json;\n\n  console.log(`Processing repo: ${forkRepo.full_name || 'N/A'}`);\n\n  if (forkRepo && forkRepo.fork && forkRepo.parent) {\n    const upstreamRepo = forkRepo.parent;\n\n    const upstreamOwner = upstreamRepo.owner.login;\n    const upstreamName = upstreamRepo.name;\n    const upstreamDefaultBranch = upstreamRepo.default_branch;\n\n    const forkOwner = forkRepo.owner.login;\n    const forkName = forkRepo.name;\n    const forkDefaultBranch = forkRepo.default_branch;\n\n    const compareUrl = `https://api.github.com/repos/${upstreamOwner}/${upstreamName}/compare/${upstreamDefaultBranch}...${forkOwner}:${forkDefaultBranch}`;\n\n    const newItem = {\n      json: {\n        // This is the URL the HTTP Request node will use\n        compareApiUrl: compareUrl,\n\n        // <<<< CRITICAL: Embed ALL original data here in a nested object >>>>\n        // This relies on the HTTP Request node passing this nested object through\n        original_repo_data: {\n          repoName: forkRepo.full_name,\n          upstreamUrl: upstreamRepo.html_url,\n          forkDefaultBranch: forkDefaultBranch,\n          upstreamDefaultBranch: upstreamDefaultBranch,\n          upstreamRepoName: upstreamName,\n          upstreamOwner: upstreamOwner,\n        }\n        // <<<< END CRITICAL >>>>\n      }\n    };\n\n    items.push(newItem);\n    console.log(`--> Prepared item for: ${forkRepo.full_name}. Compare URL: ${compareUrl}`);\n\n  } else {\n    let skipReason = \"Not a fork or missing parent\";\n    if (forkRepo) {\n        if (!forkRepo.fork) skipReason = \"Not marked as a fork\";\n        if (!forkRepo.parent) skipReason = \"Missing parent (upstream) repository\";\n    }\n    console.log(`Skipping repo: ${forkRepo.full_name || 'N/A'} - Reason: ${skipReason}`);\n  }\n}\n\nconsole.log(`Code Node (Prepare Upstream URL): Returning ${items.length} items.`);\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "52d91917-e7d0-4a5a-b73b-d7c6f4041603",
      "name": "Compare Branches API Aufruf",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1820,
        -80
      ],
      "parameters": {
        "url": "={{ $json.compareApiUrl }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github.v3+json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "y5bKE9liUU2yNx4X",
          "name": "github-api-pat"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f08f4f87-d7a6-49d4-99a2-04790a98b58d",
      "name": "Vergleichsergebnis verarbeiten",
      "type": "n8n-nodes-base.code",
      "position": [
        2040,
        -80
      ],
      "parameters": {
        "jsCode": "const items = [];\n\nconsole.log(`Code Node (Process Combined Result): Received ${$input.all().length} input items.`);\n\nfor (const item of $input.all()) {\n  // Original repo data is picked up from 2 nodes earlier\n  const originalRepoData = $('Prepare Upstream URL').first().json.original_repo_data\n\n  // API response data is now in item.json\n  const apiResponse = item.json;\n  console.log(\"apiResponse\",apiResponse);\n\n\n  // Debugging: Check if data is available\n  if (typeof apiResponse.behind_by === 'undefined' || typeof apiResponse.ahead_by === 'undefined') {\n      console.error(`ERROR: Missing or malformed data after merge! Original:`, originalRepoData, `API:`, apiResponse);\n      // Push an error item or skip\n      items.push({\n          json: {\n              repoName: originalRepoData?.repoName || 'N/A', // Use optional chaining for safety\n              status: \"ERROR_PROCESSING_MERGE\",\n              commitsBehind: -1,\n              commitsAhead: -1\n          }\n      });\n      continue;\n  }\n\n  const commitsBehind = apiResponse.behind_by;\n  const commitsAhead = apiResponse.ahead_by;\n  const status = commitsBehind > 0 ? \"BEHIND\" : (commitsAhead > 0 ? \"AHEAD\" : \"UP-TO-DATE\");\n\n\n\n  const processedItem = {\n    json: {\n      repoName: originalRepoData.repoName,\n      upstreamUrl: originalRepoData.upstreamUrl,\n      forkDefaultBranch: originalRepoData.forkDefaultBranch,\n      upstreamDefaultBranch: originalRepoData.upstreamDefaultBranch,\n      upstreamRepoName: originalRepoData.upstreamRepoName,\n      upstreamOwner: originalRepoData.upstreamOwner,\n      status: status,\n      commitsBehind: commitsBehind,\n      commitsAhead: commitsAhead\n    }\n  };\n  items.push(processedItem);\n  console.log(`--> Processed ${processedItem.json.repoName}: Status=${processedItem.json.status}, Behind=${processedItem.json.commitsBehind}, Ahead=${processedItem.json.commitsAhead}`);\n}\nconsole.log(`Code Node (Process Combined Result): Returning ${items.length} items.`);\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "19d0624b-1773-41db-8a37-bbee490b7f50",
      "name": "Ergebnisse zusammenführen",
      "type": "n8n-nodes-base.merge",
      "position": [
        2260,
        0
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "5f4fd86e-9c37-4bb3-a55f-2db724ed9bd4",
      "name": "Für Telegram formatieren",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        -480
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nlet telegramMessage = `*GitHub Fork Status Report - ${new Date().toLocaleDateString()}*\\n\\n`;\n\n// Initialize counters for summary\nlet reposBehind = 0;\nlet reposAhead = 0;\nlet reposUpToDate = 0;\n\n// ... (previous code)\n\n// Build the table header\ntelegramMessage += `| Ahead | Behind | Repository |\\n`;\ntelegramMessage += `|------------|------------|-----------------------|\\n`;\n\n// Populate table rows\nfor (const item of items) {\n    const data = item.json;\n    const repoName = data.repoName;\n    const commitsAhead = data.commitsAhead;\n    const commitsBehind = data.commitsBehind;\n\n    // Update counters for summary\n    if (commitsBehind > 0) {\n        reposBehind++;\n    } else if (commitsAhead > 0) {\n        reposAhead++;\n    } else {\n        reposUpToDate++;\n        continue;\n    }\n\n    // --- CRITICAL FIX START ---\n    // Ensure you use BACKTICKS ( ` ) for the string literal\n    // And standard JavaScript template literal syntax ${variableName}\n\n    // Escape characters in the repo name for Markdown if necessary, but usually not for links like this\n    // For general text: `repoName.replace(/[-_.*[\\]()~`>#+\\-=|{}.!]/g, '\\\\$&')`\n    // But for links, usually the raw name is fine within `[]`\n    const escapedRepoName = repoName.replace(/_/g, '\\\\_').replace(/\\*/g, '\\\\*').replace(/\\[/g, '\\\\[').replace(/\\]/g, '\\\\]').replace(/\\(/g, '\\\\(').replace(/\\)/g, '\\\\)').replace(/~/g, '\\\\~').replace(/`/g, '\\\\`').replace(/>/g, '\\\\>').replace(/#/g, '\\\\#').replace(/\\+/g, '\\\\+').replace(/-/g, '\\\\-').replace(/=/g, '\\\\=').replace(/\\|/g, '\\\\|').replace(/{/g, '\\\\{').replace(/}/g, '\\\\}').replace(/\\./g, '\\\\.').replace(/!/g, '\\\\!');\n\n\n    // Use [text](url) format for Telegram MarkdownV2 links\n    const repoLink = `[${escapedRepoName}](https://github.com/${repoName})`; // Changed from <url|text>\n    // --- CRITICAL FIX END ---\n  // Format commitsAhead and commitsBehind to be 4 characters wide, right-aligned\n    const formattedAhead = String(commitsAhead).padStart(5, '0');\n    const formattedBehind = String(commitsBehind).padStart(5, '0');\n    telegramMessage += `| ${formattedAhead} | ${formattedBehind} | ${repoLink} |\\n`;\n}\n\n// ... (rest of the code)\n\n\nreturn [{ json: { telegramMessage: telegramMessage } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ea7c4abd-2748-4e9e-8e44-eefb60757db6",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        180,
        -40
      ],
      "parameters": {
        "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n  item.json.repos = processCommand(item.json.message.text);\n}\n\nreturn $input.all();\n\nfunction processCommand(input) {\n  const parts = input.trim().split(\" \"); // Split by space and remove extra spaces\n  const command = parts[0];\n  const number = parseInt(parts[1], 10); // Convert second part to an integer\n\n  let repos = 0;\n\n  if (command === \"/forkcheck\") {\n    if (Number.isInteger(number) && number > 0) {\n      repos = number;\n    } else {\n      repos = 1000; // Default value if blank or 0\n    }\n    console.log(`Repos set to: ${repos}`);\n  } else {\n    console.log(\"Invalid command.\");\n  }\nreturn repos;\n}\n\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d9aebe90-f406-4152-bf15-dc73aeacdb6d",
      "name": "Wenn",
      "type": "n8n-nodes-base.if",
      "position": [
        380,
        -40
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ae98c5c9-a1df-4458-862f-32555165bae1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.repos }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d926e485-2d76-4c10-a231-5d5e4cc6b1ae",
      "name": "Notizzettel",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -200,
        -400
      ],
      "parameters": {
        "width": 800,
        "height": 640,
        "content": "## Triggers - *Set your triggers here*\n\n\n\n## Default\n- Manual\n- Telegram bot with command /forkcheck\n\n## Usage\n- /forkcheck = 1000 repositoriesmax\n- /forkcheck n = n repositories\n- any other command is rejected\n"
      },
      "typeVersion": 1
    },
    {
      "id": "53fde103-cbeb-4932-9027-f779c93b441c",
      "name": "Notizzettel1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        620,
        -400
      ],
      "parameters": {
        "color": 5,
        "width": 420,
        "height": 640,
        "content": "## Filter repositories\n\nSelect only forked repositories\nRequired argument: Count to select repositories"
      },
      "typeVersion": 1
    },
    {
      "id": "45b0716e-993f-478a-af4f-ead51f5132c5",
      "name": "Notizzettel2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1060,
        -260
      ],
      "parameters": {
        "width": 1360,
        "height": 500,
        "content": "## Processing logic"
      },
      "typeVersion": 1
    },
    {
      "id": "b7349c8b-d4cb-4636-a788-673dfc4fff1a",
      "name": "Repositories abrufen",
      "type": "n8n-nodes-base.github",
      "position": [
        660,
        -80
      ],
      "webhookId": "0ae11593-7d12-4b7d-ae1a-41b65bb6b34d",
      "parameters": {
        "limit": "={{ $json.repos }}",
        "owner": {
          "__rl": true,
          "mode": "list",
          "value": "mk-in",
          "cachedResultUrl": "https://github.com/mk-in",
          "cachedResultName": "mk-in"
        },
        "resource": "user"
      },
      "credentials": {
        "githubApi": {
          "id": "qdeIXe3PnQJwdyiy",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "c85f5822-2b50-4f1d-b479-47ff8c4ef6e6",
      "name": "Notizzettel3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1060,
        -580
      ],
      "parameters": {
        "color": 3,
        "width": 1360,
        "height": 300,
        "content": "## Summarize and send"
      },
      "typeVersion": 1
    },
    {
      "id": "e5c4395c-8598-4da5-87a5-f8e5395ada44",
      "name": "Notizzettel4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -200,
        -580
      ],
      "parameters": {
        "width": 1240,
        "content": "## GitHub Fork Status Monitor\n\nThis workflow helps you keep an eye on your GitHub forks, notifying you when they fall behind or pull ahead of their upstream repositories."
      },
      "typeVersion": 1
    },
    {
      "id": "bd807482-d2c7-4cb8-aab6-854e816ab25a",
      "name": "Nur geforkte Repositories filtern",
      "type": "n8n-nodes-base.filter",
      "position": [
        880,
        -80
      ],
      "parameters": {
        "options": {
          "ignoreCase": false
        },
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "651ce98d-bf86-474a-b376-05fb5233fbee",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.fork }}",
              "rightValue": "true"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a9ced36c-710d-4306-a51d-50b419cf8c86",
  "connections": {
    "d9aebe90-f406-4152-bf15-dc73aeacdb6d": {
      "main": [
        [
          {
            "node": "b7349c8b-d4cb-4636-a788-673dfc4fff1a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ea7c4abd-2748-4e9e-8e44-eefb60757db6": {
      "main": [
        [
          {
            "node": "d9aebe90-f406-4152-bf15-dc73aeacdb6d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "b7349c8b-d4cb-4636-a788-673dfc4fff1a": {
      "main": [
        [
          {
            "node": "bd807482-d2c7-4cb8-aab6-854e816ab25a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "19d0624b-1773-41db-8a37-bbee490b7f50": {
      "main": [
        [
          {
            "node": "5d55b266-a8b8-450f-903c-b04f47a1d708",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5d55b266-a8b8-450f-903c-b04f47a1d708": {
      "main": [
        [
          {
            "node": "5f4fd86e-9c37-4bb3-a55f-2db724ed9bd4",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "c160d1cf-67a0-4e5a-9a0d-f6c78ad18154",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f0170087-277f-4f32-bc45-305dd56211df": {
      "main": [
        [
          {
            "node": "ea7c4abd-2748-4e9e-8e44-eefb60757db6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "c160d1cf-67a0-4e5a-9a0d-f6c78ad18154": {
      "main": [
        [
          {
            "node": "f22d2c47-5d0a-4574-8293-e7ddc9a326ac",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5f4fd86e-9c37-4bb3-a55f-2db724ed9bd4": {
      "main": [
        [
          {
            "node": "7580ab52-4cfc-490f-9f3e-021bba782db4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f22d2c47-5d0a-4574-8293-e7ddc9a326ac": {
      "main": [
        [
          {
            "node": "52d91917-e7d0-4a5a-b73b-d7c6f4041603",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "52d91917-e7d0-4a5a-b73b-d7c6f4041603": {
      "main": [
        [
          {
            "node": "f08f4f87-d7a6-49d4-99a2-04790a98b58d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f08f4f87-d7a6-49d4-99a2-04790a98b58d": {
      "main": [
        [
          {
            "node": "19d0624b-1773-41db-8a37-bbee490b7f50",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "bd807482-d2c7-4cb8-aab6-854e816ab25a": {
      "main": [
        [
          {
            "node": "5d55b266-a8b8-450f-903c-b04f47a1d708",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d27cc197-fa61-421b-8422-8c4e5f716efe": {
      "main": [
        [
          {
            "node": "b7349c8b-d4cb-4636-a788-673dfc4fff1a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Häufig gestellte Fragen

Wie verwende ich diesen Workflow?

Kopieren Sie den obigen JSON-Code, erstellen Sie einen neuen Workflow in Ihrer n8n-Instanz und wählen Sie "Aus JSON importieren". Fügen Sie die Konfiguration ein und passen Sie die Anmeldedaten nach Bedarf an.

Für welche Szenarien ist dieser Workflow geeignet?

Experte - Engineering

Ist es kostenpflichtig?

Dieser Workflow ist völlig kostenlos. Beachten Sie jedoch, dass Drittanbieterdienste (wie OpenAI API), die im Workflow verwendet werden, möglicherweise kostenpflichtig sind.

Workflow-Informationen
Schwierigkeitsgrad
Experte
Anzahl der Nodes19
Kategorie1
Node-Typen11
Schwierigkeitsbeschreibung

Für fortgeschrittene Benutzer, komplexe Workflows mit 16+ Nodes

Externe Links
Auf n8n.io ansehen

Diesen Workflow teilen

Kategorien

Kategorien: 34