8
n8n 中文网amn8n.com

每日MLB投手vs击球手对阵分析(Google Sheets + Telegram)

中级

这是一个Miscellaneous, Multimodal AI领域的自动化工作流,包含 14 个节点。主要使用 Code, Telegram, HttpRequest, GoogleSheets, ScheduleTrigger 等节点。 使用Google Sheets和Telegram的每日MLB投手vs击球手对阵分析

前置要求
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
  • Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "4b9ebdcb82324c7ebd0d3194b3afce46ddeac81826241b95da802461b66d7743"
  },
  "nodes": [
    {
      "id": "9a8548ad-12f1-41a7-9226-c8d06806b7b2",
      "name": "3. 提取所有玩家 ID",
      "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. 获取批量玩家统计数据",
      "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. 获取每日比赛",
      "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. 创建最终对阵行",
      "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. 筛选顶级对阵",
      "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": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -368
      ],
      "parameters": {
        "color": 4,
        "width": 2316,
        "height": 500,
        "content": "安打数"
      },
      "typeVersion": 1
    },
    {
      "id": "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe",
      "name": "列顺序",
      "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": "上午 9 点清除",
      "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": "备注:MLB 安打数",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -848
      ],
      "parameters": {
        "width": 752,
        "height": 656,
        "content": "MLB\"安打数\"工作流 — 概述"
      },
      "typeVersion": 1
    },
    {
      "id": "6ef30ef8-0023-42bc-b322-2fffd650894b",
      "name": "8. 21 名击球员",
      "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": "清除您的表格",
      "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. 更新您的表格",
      "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. 发送到Telegram聊天机器人",
      "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": {
    "9am Clear": {
      "main": [
        [
          {
            "node": "Clear your Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11:02 - 8:02": {
      "main": [
        [
          {
            "node": "2. Get Daily Games",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Column Order": {
      "main": [
        [
          {
            "node": "7. Update Your Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8.  21 Hitters": {
      "main": [
        [
          {
            "node": "9. sendToTelegramChatbot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Get Daily Games": {
      "main": [
        [
          {
            "node": "3. Extract All Player IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Update Your Sheet": {
      "main": [
        [
          {
            "node": "8.  21 Hitters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Extract All Player IDs": {
      "main": [
        [
          {
            "node": "4. Get Batched Player Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Get Batched Player Stats": {
      "main": [
        [
          {
            "node": "5. Create Final Matchup Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6.  Filter for Top Matchups": {
      "main": [
        [
          {
            "node": "Column Order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Create Final Matchup Rows": {
      "main": [
        [
          {
            "node": "6.  Filter for Top Matchups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 杂项, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量14
分类2
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流