Analyse quotidienne des matcheurs lanceurs vs frappeurs MLB (Google Sheets + Telegram)

Intermédiaire

Ceci est unMiscellaneous, Multimodal AIworkflow d'automatisation du domainecontenant 14 nœuds.Utilise principalement des nœuds comme Code, Telegram, HttpRequest, GoogleSheets, ScheduleTrigger. Analyse quotidienne des confrontations lanceurs vs frappeurs MLB avec Google Sheets et Telegram

Prérequis
  • Token Bot Telegram
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
  • Informations d'identification Google Sheets API
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "meta": {
    "instanceId": "4b9ebdcb82324c7ebd0d3194b3afce46ddeac81826241b95da802461b66d7743"
  },
  "nodes": [
    {
      "id": "9a8548ad-12f1-41a7-9226-c8d06806b7b2",
      "name": "3. Extraire tous les ID de joueurs",
      "type": "n8n-nodes-base.code",
      "notes": "who will be playing today.",
      "position": [
        32,
        -128
      ],
      "parameters": {
        "jsCode": "const allPlayerIds = new Set();\nconst allGames = items[0].json.dates[0]?.games || [];\n\nif (allGames.length === 0) {\n  return [];\n}\n\n// Loop through all games to find every player\nfor (const game of allGames) {\n  if (game.teams?.home?.probablePitcher?.id) {\n    allPlayerIds.add(game.teams.home.probablePitcher.id);\n  }\n  if (game.teams?.away?.probablePitcher?.id) {\n    allPlayerIds.add(game.teams.away.probablePitcher.id);\n  }\n  if (game.lineups && Array.isArray(game.lineups.homePlayers)) {\n    for (const player of game.lineups.homePlayers) {\n      if (player.id) allPlayerIds.add(player.id);\n    }\n  }\n  if (game.lineups && Array.isArray(game.lineups.awayPlayers)) {\n    for (const player of game.lineups.awayPlayers) {\n      if (player.id) allPlayerIds.add(player.id);\n    }\n  }\n}\n\nif (allPlayerIds.size === 0) {\n  return [];\n}\n\n// We pass the original game data THROUGH this node by adding it to the output\nconst output = items[0].json;\noutput.playerIdsString = Array.from(allPlayerIds).join(',');\nreturn [{ json: output }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72",
      "name": "4. Obtenir les statistiques des joueurs par lots",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        256,
        -128
      ],
      "parameters": {
        "url": "https://statsapi.mlb.com/api/v1/people",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "personIds",
              "value": "={{$json.playerIdsString}}"
            },
            {
              "name": "hydrate",
              "value": "stats(group=[pitching,hitting,fielding],type=[season])"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ff23a51f-3dc8-4b85-97b0-611e0bb6a304",
      "name": "2. Obtenir les matchs quotidiens",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -176,
        -128
      ],
      "parameters": {
        "url": "https://statsapi.mlb.com/api/v1/schedule",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "sportId",
              "value": "1"
            },
            {
              "name": "date",
              "value": "={{ $now.toFormat('yyyy-MM-dd') }}"
            },
            {
              "name": "hydrate",
              "value": "probablePitcher,lineups"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "32ccdb2c-0b9d-4af4-9590-c58b9902c31c",
      "name": "5. Créer les lignes finales de duels",
      "type": "n8n-nodes-base.code",
      "notes": "game schedule data and merges it with the detailed player stats",
      "position": [
        496,
        -128
      ],
      "parameters": {
        "jsCode": "const allMatchupRows = [];\n\n// Get player stats from the direct input of this node\nconst playerStatsData = items[0].json;\n\n// Get the original game data by looking back at the previous node\nconst scheduleNode = $('3. Extract All Player IDs').first();\nconst originalScheduleData = scheduleNode.json;\nconst games = originalScheduleData.dates[0]?.games || [];\n\nconst playersWithStats = playerStatsData.people || [];\n\nif (games.length === 0 || playersWithStats.length === 0) {\n  return [];\n}\n\nconst statsMap = new Map(playersWithStats.map(p => [p.id, p]));\n\nfor (const game of games) {\n\n  const gameStartTime = game.gameDate || 'N/A';\n\n  // --- Home Pitcher vs Away Batters ---\n  const homePitcherId = game.teams?.home?.probablePitcher?.id;\n  const awayLineup = game.lineups?.awayPlayers;\n\n  if (homePitcherId && Array.isArray(awayLineup)) {\n    const pitcherData = statsMap.get(homePitcherId);\n    if (pitcherData) {\n      for (const batter of awayLineup) {\n        const batterData = statsMap.get(batter.id);\n        if (batterData) {\n          const pitcherStats = pitcherData.stats?.find(s => s.group?.displayName === 'pitching')?.splits[0]?.stat || {};\n          const batterStats = batterData.stats?.find(s => s.group?.displayName === 'hitting')?.splits[0]?.stat || {};\n          allMatchupRows.push({\n            gameStartTime: gameStartTime,\n            gameDate: game.officialDate || 'N/A',\n            pitcherName: pitcherData.fullName || 'N/A',\n            pitcherTeam: game.teams?.home?.team?.name || 'N/A',\n            pitcherThrows: pitcherData.pitchHand?.description || 'N/A',\n            pitcherERA: pitcherStats.era,\n            pitcherSO: pitcherStats.strikeOuts,\n            opponent: game.teams?.away?.team?.name || 'N/A',\n            batterName: batterData.fullName || 'N/A',\n            batterPosition: batter.primaryPosition?.abbreviation || 'N/A',\n            batterBats: batterData.batSide?.description || 'N/A',\n            batterAVG: batterStats.avg,\n            batterOPS: batterStats.ops,\n            batterHR: batterStats.homeRuns,\n            batterHits: batterStats.hits,\n            batterRBI: batterStats.rbi,\n            pitcherId: pitcherData.id, // ADDED: Pitcher ID\n            batterId: batterData.id,   // ADDED: Batter ID\n          });\n        }\n      }\n    }\n  }\n\n  // --- Away Pitcher vs Home Batters ---\n  const awayPitcherId = game.teams?.away?.probablePitcher?.id;\n  const homeLineup = game.lineups?.homePlayers;\n\n  if (awayPitcherId && Array.isArray(homeLineup)) {\n    const pitcherData = statsMap.get(awayPitcherId);\n    if (pitcherData) {\n      for (const batter of homeLineup) {\n        const batterData = statsMap.get(batter.id);\n        if (batterData) {\n          const pitcherStats = pitcherData.stats?.find(s => s.group?.displayName === 'pitching')?.splits[0]?.stat || {};\n          const batterStats = batterData.stats?.find(s => s.group?.displayName === 'hitting')?.splits[0]?.stat || {};\n          allMatchupRows.push({\n            gameStartTime: gameStartTime,\n            gameDate: game.officialDate || 'N/A',\n            pitcherName: pitcherData.fullName || 'N/A',\n            pitcherTeam: game.teams?.away?.team?.name || 'N/A',\n            pitcherThrows: pitcherData.pitchHand?.description || 'N/A',\n            pitcherERA: pitcherStats.era,\n            pitcherSO: pitcherStats.strikeOuts,\n            opponent: game.teams?.home?.team?.name || 'N/A',\n            batterName: batterData.fullName || 'N/A',\n            batterPosition: batter.primaryPosition?.abbreviation || 'N/A',\n            batterBats: batterData.batSide?.description || 'N/A',\n            batterAVG: batterStats.avg,\n            batterOPS: batterStats.ops,\n            batterHR: batterStats.homeRuns,\n            batterHits: batterStats.hits,\n            batterRBI: batterStats.rbi,\n            pitcherId: pitcherData.id, // ADDED: Pitcher ID\n            batterId: batterData.id,   // ADDED: Batter ID\n          });\n        }\n      }\n    }\n  }\n}\n\nreturn allMatchupRows.map(row => ({ json: row }));"
      },
      "notesInFlow": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "73bac962-fa64-45dc-86bd-671a17488992",
      "name": "6. Filtrer pour les meilleurs duels",
      "type": "n8n-nodes-base.code",
      "notes": "Pandas to filter and sort before Sheets",
      "position": [
        736,
        -128
      ],
      "parameters": {
        "jsCode": "// Convert to Eastern Time using proper timezone handling\nfunction convertToEasternTime(isoString) {\n\tconst date = new Date(isoString);\n\treturn date.toLocaleTimeString('en-US', {\n\t\thour: '2-digit',\n\t\tminute: '2-digit',\n\t\thour12: true,\n\t\ttimeZone: 'America/New_York'\n\t});\n}\n\nconst HEADER_ORDER = [\n\t\"gameDate\",\n\t\"gameStartTime\",\n\t\"pitcherId\",\n\t\"pitcherName\",\n\t\"pitcherTeam\",\n\t\"pitcherSO\",\n\t\"pitcherERA\",\n\t\"pitcherThrows\",\n\t\"batterBats\",\n\t\"opponent\",\n\t\"batterName\",\n\t\"batterAVG\",\n\t\"batterHR\",\n\t\"batterHits\",\n\t\"batterOPS\",\n\t\"batterRBI\",\n\t\"batterId\",\n\t\"batterPosition\"\n];\n\nconst allMatchups = items.map(item => item.json);\n\n// Filter valid rows\nconst valid = allMatchups.filter(m => {\n\tconst era = parseFloat(m.pitcherERA);\n\tconst ops = parseFloat(m.batterOPS);\n\treturn !isNaN(era) && era > 3.33 && !isNaN(ops) && m.gameStartTime;\n});\n\n// Parse and add helper fields\nvalid.forEach(m => {\n\tm.era = parseFloat(m.pitcherERA);\n\tm.ops = parseFloat(m.batterOPS);\n\tm.gameStartObj = new Date(m.gameStartTime);\n});\n\n// Step 1: Get top 9 unique pitchers with highest ERA\nconst seenPitchers = new Set();\nconst topPitchers = [];\n\nvalid\n\t.sort((a, b) => b.era - a.era)\n\t.forEach(m => {\n\t\tif (!seenPitchers.has(m.pitcherName)) {\n\t\t\tseenPitchers.add(m.pitcherName);\n\t\t\ttopPitchers.push(m.pitcherName);\n\t\t}\n\t});\n\nconst top9 = topPitchers.slice(0, 9);\n\n// Step 2: For each of these pitchers, get top 3 batters by OPS\nconst final = [];\n\ntop9.forEach(pitcherName => {\n\tconst matchups = valid.filter(m => m.pitcherName === pitcherName);\n\tconst seenBatters = new Set();\n\tconst topBatters = [];\n\n\tmatchups\n\t\t.sort((a, b) => b.ops - a.ops)\n\t\t.forEach(m => {\n\t\t\tif (!seenBatters.has(m.batterId)) {\n\t\t\t\tseenBatters.add(m.batterId);\n\t\t\t\ttopBatters.push(m);\n\t\t\t}\n\t\t});\n\n\tfinal.push(...topBatters.slice(0, 3));\n});\n\n// Final sort by game start time\nfinal.sort((a, b) => a.gameStartObj - b.gameStartObj);\n\n// Format time and construct output with locked header order\nconst output = final.map(m => {\n\tm.gameStartTime = convertToEasternTime(m.gameStartObj.toISOString());\n\tconst obj = {};\n\tHEADER_ORDER.forEach(key => obj[key] = m[key] ?? '');\n\treturn { json: obj };\n});\n\nreturn output;\n"
      },
      "notesInFlow": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "7d9f19aa-a8a6-4f50-8462-0cad13de6e9c",
      "name": "Note adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -368
      ],
      "parameters": {
        "color": 4,
        "width": 2316,
        "height": 500,
        "content": "Hits"
      },
      "typeVersion": 1
    },
    {
      "id": "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe",
      "name": "Ordre des colonnes",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        -128
      ],
      "parameters": {
        "jsCode": "const orderedKeys = [\n  \"gameDate\",\n  \"gameStartTime\",\n  \"pitcherId\",\n  \"pitcherName\",\n  \"pitcherTeam\",\n  \"pitcherSO\",\n  \"pitcherERA\",\n  \"pitcherThrows\",\n  \"batterBats\",\n  \"opponent\",\n  \"batterName\",\n  \"batterAVG\",\n  \"batterHR\",\n  \"batterHits\",\n  \"batterOPS\",\n  \"batterRBI\",\n  \"batterId\",\n  \"batterPosition\"\n];\n\n// fields that should explicitly be numbers\nconst numericKeys = [\n  \"pitcherId\",\n  \"pitcherSO\",\n  \"pitcherERA\",\n  \"batterAVG\",\n  \"batterHR\",\n  \"batterHits\",\n  \"batterOPS\",\n  \"batterRBI\",\n  \"batterId\"\n];\n\n// 🔷 Helper: format to hh:mm AM/PM ET\nfunction toEasternTimeHHMM(value) {\n  if (typeof value === 'string' && /^\\d{1,2}:\\d{2}/.test(value)) {\n    return value.replace(/:00$/, ''); // clean up trailing :00 if it’s there\n  }\n  try {\n    const utcDate = new Date(value);\n    if (isNaN(utcDate.getTime())) return value;\n    const options = {\n      timeZone: 'America/New_York',\n      hour: '2-digit',\n      minute: '2-digit',\n      hour12: true\n    };\n    return new Intl.DateTimeFormat('en-US', options).format(utcDate);\n  } catch {\n    return value;\n  }\n}\n\n// Build new list\nconst enriched = items.map(item => {\n  const json = { ...item.json };\n\n  if (json.gameStartTime) {\n    json.gameStartTime = toEasternTimeHHMM(json.gameStartTime);\n  }\n\n  const output = {};\n  for (const key of orderedKeys) {\n    let val = json[key] ?? \"\";\n    if (numericKeys.includes(key) && val !== \"\") {\n      val = Number(val);\n\n      // Round batterAVG and batterOPS to .000\n      if (key === \"batterAVG\" || key === \"batterOPS\") {\n        val = Number(val.toFixed(3));\n      }\n    }\n    output[key] = val;\n  }\n\n  return { json: output };\n});\n\n// Sort enriched list\nenriched.sort((a, b) => {\n  const parseTime = (timeStr) => {\n    const [time, meridian] = timeStr.split(' ');\n    let [hours, minutes] = time.split(':').map(Number);\n    if (meridian === 'PM' && hours !== 12) hours += 12;\n    if (meridian === 'AM' && hours === 12) hours = 0;\n    return hours * 60 + minutes; // total minutes since midnight\n  };\n\n  const aTime = parseTime(a.json.gameStartTime);\n  const bTime = parseTime(b.json.gameStartTime);\n  if (aTime < bTime) return -1;\n  if (aTime > bTime) return 1;\n\n  const aPitcher = Number(a.json.pitcherId) || 0;\n  const bPitcher = Number(b.json.pitcherId) || 0;\n  if (aPitcher < bPitcher) return -1;\n  if (aPitcher > bPitcher) return 1;\n\n  const aOpp = a.json.opponent || \"\";\n  const bOpp = b.json.opponent || \"\";\n  return aOpp.localeCompare(bOpp);\n});\n\n\nreturn enriched;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "09b74296-10fa-43a4-8fb4-65a017dd9f78",
      "name": "9h Effacer",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -400,
        -304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            },
            {
              "triggerAtHour": 9,
              "triggerAtMinute": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "31005912-843f-4c2f-8614-c92c00404fb8",
      "name": "11:02 - 8:02",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "02 11-20 * * *",
      "position": [
        -384,
        -128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "02 11-20 * * *"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "e7d4e4e6-09f1-4ce0-820c-a60cd795a858",
      "name": "Notes : MLB Hits",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -848
      ],
      "parameters": {
        "width": 752,
        "height": 656,
        "content": "MLB \"Hits\" Workflow — Overview\n• Pulls today's MLB schedule incl. probablePitcher + lineups (statsapi.mlb.com)\n• Batches season stats for all involved players\n• Builds pitcher vs. batter matchup rows\n• Filters: ERA > 3.33, take top 9 pitchers by ERA, then top 3 opposing batters by OPS each (27 rows)\n• Sorts by ET start time, writes to Google Sheets (Your Google SHeet → the tabName)\n• Sends Telegram message: Top 21 batters by OPS\n\nSetup\n1) Google Sheets OAuth2 → point to your Google Sheet → Tab name\n2) Telegram Bot cred + chatId\n3) Optional: tweak ERA/OPS thresholds or Top N in nodes 6 or 8.\n\nTriggers\n• \"11:02 – 8:02\" runs hourly at :02 (server time)\n• \"9am Clear\" clears the sheet at 09:00 & 09:15\n\nKey Nodes\n• 2. Get Daily Games — GET /schedule (date=today; hydrate=probablePitcher,lineups)\n• 3. Extract All Player IDs — collects personIds (pitchers + lineups)\n• 4. Get Batched Player Stats — GET /people?personIds=...&hydrate=stats(...)\n• 5. Create Final Matchup Rows — merges schedule + stats into pitcher↔batter rows\n• 6. Filter for Top Matchups — ERA/OPS filter; pick top 9×3; convert times to ET\n• Column Order — enforce column order, numeric typing, rounding (.000 for AVG/OPS)\n• 7. Update n8n-sheet — append/update by batterId\n• 8. 21 Hitters — composes Top 21 by OPS message\n• 9. Sends Telegram\n\nNotes\n• If lineups/schedule not ready yet, downstream may be empty (expected)\n• Keep node name \"3. Extract All Player IDs\" exact (used by $())\n• ET conversion is in code; cron uses server time"
      },
      "typeVersion": 1
    },
    {
      "id": "6ef30ef8-0023-42bc-b322-2fffd650894b",
      "name": "8. 21 Batteurs",
      "type": "n8n-nodes-base.code",
      "position": [
        1408,
        -128
      ],
      "parameters": {
        "jsCode": "// This code is for an n8n Code node.\n// It assumes the input from the preceding node (which now provides all 27 batters)\n// allows 'items' to contain all batter records.\n\nconsole.log(\"--- Code Node for Telegram Message Start ---\");\n\n// --- Input Processing (same as before to get all 27 batters) ---\nlet allBatterStats = [];\n\n// This block handles both scenarios:\n// 1. If \"Run Once for All Items\" is ON, 'items' contains all incoming records.\n// 2. If an \"Item Lists (Aggregate)\" node precedes this, 'items[0].json' will be the array.\nfor (const item of items) {\n  // Scenario A: Item contains a single object (e.g., from a direct data source outputting individual items)\n  if (item && typeof item.json === 'object' && item.json !== null && !Array.isArray(item.json)) {\n    allBatterStats.push(item.json);\n  }\n  // Scenario B: Item.json is an array (e.g., from an Item Lists aggregate node, or if a source directly outputs a single array)\n  else if (item && Array.isArray(item.json)) {\n    allBatterStats.push(...item.json);\n  }\n  // Fallback: If the item itself (not its .json) is the object, or other unexpected structures\n  else if (typeof item === 'object' && item !== null && item.batterName && item.batterHits) {\n      allBatterStats.push(item);\n  } else {\n      console.warn(\"Skipping an input item with an unrecognized structure:\", JSON.stringify(item, null, 2));\n  }\n}\n\nconsole.log(\"Total batter stats collected:\", allBatterStats.length);\n\n// Ensure batterHits is a number and filter out invalid entries.\nconst processedBatterStats = allBatterStats.map(batter => {\n    const newBatter = { ...batter };\n    newBatter.batterHits = typeof batter.batterHits === 'string'\n                           ? parseFloat(batter.batterHits)\n                           : batter.batterHits;\n    if (isNaN(newBatter.batterHits)) {\n        newBatter.batterHits = 0; // Default invalid numbers to 0\n    }\n    return newBatter;\n}).filter(batter =>\n    typeof batter === 'object' &&\n    batter !== null &&\n    typeof batter.batterHits === 'number' &&\n    typeof batter.batterName === 'string' &&\n    batter.batterName.trim() !== ''\n);\n\nconsole.log(\"Number of valid and processed batter stats:\", processedBatterStats.length);\n\nif (processedBatterStats.length === 0) {\n  console.warn(\"No valid batter stats found. Returning empty message.\");\n  return [{ json: { text: \"No batter stats available to display.\" } }];\n}\n\n// Sort the stats by batterHits in descending order.\nprocessedBatterStats.sort((a, b) => b.batterOPS - a.batterOPS);\n\n// Get the top 21 batters.\nconst top21Batters = processedBatterStats.slice(0, 21);\n\nconsole.log(\"Number of top 21 batters selected:\", top21Batters.length);\n\n// --- Telegram Message Formatting ---\n\n// Start the message with a title. Using Markdown for bold.\nlet telegramMessage = \"⚾ **Top 21 Batters by OPS** ⚾\\n\\n\";\n\n// Add each batter to the message string.\ntop21Batters.forEach((batter, index) => {\n    // Format each line using Markdown for name and hits.\n    // Remember to escape any special Markdown characters in the data if necessary,\n    // though typically names and numbers are safe.\n    telegramMessage += `${index + 1}. **${batter.batterName}**: ${batter.batterOPS}\\n`;\n});\n\nconsole.log(\"Generated Telegram message length:\", telegramMessage.length);\n\n// --- Return as a single n8n item for the Telegram node ---\n// The Telegram node's 'Text' field will consume the 'text' property from this output.\nreturn [{\n  json: {\n    message: telegramMessage // This is the single string containing the entire message\n  }\n}];"
      },
      "executeOnce": false,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "5d726f93-c7f4-42d6-b0a0-c0ff8abc159b",
      "name": "Vider votre feuille",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -176,
        -304
      ],
      "parameters": {
        "operation": "clear",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "bL32JLOAfaCKsZaj",
          "name": "Google Sheets account 2"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "29d5c57d-4f31-4fcd-aea0-865341d60d9c",
      "name": "7. Mettre à jour votre feuille",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueErrorOutput",
      "position": [
        1168,
        -128
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "bL32JLOAfaCKsZaj",
          "name": "Google Sheets account 2"
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 4,
      "alwaysOutputData": true,
      "waitBetweenTries": 5000
    },
    {
      "id": "e9c4377c-93c9-4286-b567-e233e5e65014",
      "name": "9. envoyerAuChatbotTelegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1600,
        -128
      ],
      "webhookId": "3f317a60-e451-4150-9b04-2f9e5427a22d",
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "createYourOwnOnTelegram@BOTFather",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "5HNLqEK3ZgiYkMmw",
          "name": "Telegram account"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.2
    }
  ],
  "pinData": {
    "9am Clear": [
      {
        "Hour": "09",
        "Year": "2025",
        "Month": "August",
        "Minute": "00",
        "Second": "32",
        "Timezone": "America/New_York (UTC-04:00)",
        "timestamp": "2025-08-04T09:00:32.005-04:00",
        "Day of week": "Monday",
        "Day of month": "04",
        "Readable date": "August 4th 2025, 9:00:32 am",
        "Readable time": "9:00:32 am"
      }
    ]
  },
  "connections": {
    "09b74296-10fa-43a4-8fb4-65a017dd9f78": {
      "main": [
        [
          {
            "node": "5d726f93-c7f4-42d6-b0a0-c0ff8abc159b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "31005912-843f-4c2f-8614-c92c00404fb8": {
      "main": [
        [
          {
            "node": "ff23a51f-3dc8-4b85-97b0-611e0bb6a304",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe": {
      "main": [
        [
          {
            "node": "29d5c57d-4f31-4fcd-aea0-865341d60d9c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6ef30ef8-0023-42bc-b322-2fffd650894b": {
      "main": [
        [
          {
            "node": "e9c4377c-93c9-4286-b567-e233e5e65014",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ff23a51f-3dc8-4b85-97b0-611e0bb6a304": {
      "main": [
        [
          {
            "node": "9a8548ad-12f1-41a7-9226-c8d06806b7b2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "29d5c57d-4f31-4fcd-aea0-865341d60d9c": {
      "main": [
        [
          {
            "node": "6ef30ef8-0023-42bc-b322-2fffd650894b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9a8548ad-12f1-41a7-9226-c8d06806b7b2": {
      "main": [
        [
          {
            "node": "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72": {
      "main": [
        [
          {
            "node": "32ccdb2c-0b9d-4af4-9590-c58b9902c31c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "73bac962-fa64-45dc-86bd-671a17488992": {
      "main": [
        [
          {
            "node": "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "32ccdb2c-0b9d-4af4-9590-c58b9902c31c": {
      "main": [
        [
          {
            "node": "73bac962-fa64-45dc-86bd-671a17488992",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Intermédiaire - Divers, IA Multimodale

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Workflows recommandés

Génération de blog WordPress optimisé pour le SEO avec Gemini, Tavily et une révision humaine
Utiliser Gemini, Tavily et une révision manuelle pour générer des articles de blog WordPress optimisés SEO
If
Set
Code
+
If
Set
Code
38 NœudsAryan Shinde
Création de contenu
Éditeur de publications X (Twitter) et Meta Threads
Publication automatique de contenu optimisé pour la plateforme sur X et Threads avec Late API et Google Sheets
If
Set
Code
+
If
Set
Code
20 NœudsFariez
Réseaux sociaux
Téléchargement automatique des Reels Instagram avec Google Drive pour le stockage et les rappels Telegram
Automatiser le téléchargement des Reels Instagram avec le stockage sur Google Drive et des rappels Telegram
If
Code
Webhook
+
If
Code
Webhook
11 NœudsAryan Shinde
Gestion de fichiers
Vérification d'expiration et rafraîchissement des offres d'emploi provenant de Google Sheets en utilisant le contrôle HTTP Last-Modified
Automatisation des rappels pour les offres d'emploi expirées avec Google Sheets, vérifications HTTP et Gmail
If
Set
Code
+
If
Set
Code
19 NœudsWeblineIndia
Ressources Humaines
Automatisation de la découverte et de la publication d'actualités avec GPT-4, Google Search API et Slack
Utiliser GPT-4, l'API de recherche Google et Slack pour automatiser la découverte et la publication de nouvelles
Code
Slack
Http Request
+
Code
Slack
Http Request
14 NœudsKalyxi Ai
Divers
💥 Créer des publicités virales avec NanoBanana et Seedance, les publier sur les médias sociaux via upload-post VIDE II
Créer des publicités multimédias virales avec l'IA : NanoBanana, Seedance et Suno pour les médias sociaux
If
Set
Code
+
If
Set
Code
45 NœudsDr. Firas
Divers
Informations sur le workflow
Niveau de difficulté
Intermédiaire
Nombre de nœuds14
Catégorie2
Types de nœuds6
Description de la difficulté

Adapté aux utilisateurs expérimentés, avec des workflows de complexité moyenne contenant 6-15 nœuds

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34