マルチプラットフォームゲーム割引追踪の自動化

中級

これはPersonal Productivity分野の自動化ワークフローで、11個のノードを含みます。主にIf, Cron, Gmail, Sqlite, Functionなどのノードを使用。 Deko Deals と Gmail アラートを使用したマルチプラットフォームゲーム割引の自動追跡

前提条件
  • Googleアカウント + Gmail API認証情報
  • ターゲットAPIの認証情報が必要な場合あり

カテゴリー

ワークフロープレビュー
ノード接続関係を可視化、ズームとパンをサポート
ワークフローをエクスポート
以下のJSON設定をn8nにインポートして、このワークフローを使用できます
{
  "nodes": [
    {
      "name": "デイリーチェック (午前8時)",
      "type": "n8n-nodes-base.cron",
      "notes": {
        "text": "### 1. Daily Check (8 AM)\n\nThis `Cron` node triggers the workflow automatically every **day at 8:00 AM** (based on your n8n server's local time zone). This allows for frequent checks for new deals.\n\n**To change the schedule:** Adjust the 'Hour' and 'Minute' fields to your preferred time and frequency.",
        "position": "right"
      },
      "position": [
        240,
        300
      ],
      "parameters": {
        "mode": "everyDay",
        "value": {
          "hour": [
            8
          ],
          "minute": [
            0
          ]
        },
        "options": {}
      },
      "typeVersion": 1,
      "id": "-8--0"
    },
    {
      "name": "Deku Deals ページ取得",
      "type": "n8n-nodes-base.httpRequest",
      "notes": {
        "text": "### 2. Fetch Deku Deals Page\n\nThis `HTTP Request` node downloads the HTML content of Deku Deals' 'Most Popular' page.\n\n**Setup:**\n1.  **URL:** The URL is pre-filled for Deku Deals' 'Most Popular' deals. You can change this to `/deals` or another specific section if you prefer.\n2.  **Response Format:** Ensure this is set to `string` (for HTML content).\n\n**Considerations:**\n* Deku Deals is generally well-structured for scraping. If their layout changes, the `HTML Extract` node will need updates.",
        "position": "right"
      },
      "position": [
        480,
        300
      ],
      "parameters": {
        "url": "https://www.dekudeals.com/most-popular",
        "options": {},
        "responseFormat": "string"
      },
      "typeVersion": 3,
      "id": "Deku-Deals--1"
    },
    {
      "name": "各ディールカード抽出",
      "type": "n8n-nodes-base.htmlExtract",
      "notes": {
        "text": "### 3. Extract Each Deal Card\n\nThis `HTML Extract` node first extracts each individual 'game card' HTML block. This makes it easier to process each deal independently in the next step.\n\n**Setup:**\n1.  **HTML:** This field is already set to `{{ $node[\"Fetch Deku Deals Page\"].json.data }}`.\n2.  **Selector:** `div.game-card` (This targets the main container for each deal).\n3.  **Attribute:** `html` (to get the full HTML content of each card).\n\n**If the layout changes:** You might need to update `div.game-card` to the new wrapper for individual game deals.",
        "position": "right"
      },
      "position": [
        720,
        300
      ],
      "parameters": {
        "html": "={{ $node[\"Fetch Deku Deals Page\"].json.data }}",
        "extractOperations": [
          {
            "options": {},
            "selector": "div.game-card",
            "attribute": "html",
            "operation": "extract",
            "propertyName": "dealHtml"
          }
        ]
      },
      "typeVersion": 1,
      "id": "--2"
    },
    {
      "name": "ディール詳細解析 (関数コード)",
      "type": "n8n-nodes-base.function",
      "notes": {
        "text": "### 4. Parse Deal Details (Function Code)\n\nThis `Function` node dives into the HTML of each deal card to extract the specific details (title, price, link, platform, etc.). It also creates a `dealUniqueId` for tracking.\n\n**Setup:**\n* This node uses JavaScript with `jsdom` (an n8n built-in library for parsing HTML) to select individual elements within each `dealHtml` item.\n* **Crucially, the selectors here (`div.name > a`, `div.price.current`, etc.) are also subject to website changes.** You might need to update them if the layout of `div.game-card` itself changes.\n\n**Output:** Each item will now have fields like `dealUniqueId`, `gameTitle`, `gameLink`, `platforms`, `currentPrice`, `originalPrice`, `discount`.",
        "position": "right"
      },
      "position": [
        960,
        300
      ],
      "parameters": {
        "options": {},
        "function": "const { JSDOM } = require('jsdom');\n\nconst deals = [];\n\nfor (const item of items) {\n  const dom = new JSDOM(item.json.dealHtml);\n  const doc = dom.window.document;\n\n  const titleElement = doc.querySelector('div.name > a');\n  const linkElement = doc.querySelector('div.name > a');\n  const currentPriceElement = doc.querySelector('div.price-wrapper > div.price.current');\n  const originalPriceElement = doc.querySelector('div.price-wrapper > div.price.original');\n  const discountElement = doc.querySelector('div.price-wrapper > div.price.discount');\n  const platformElements = doc.querySelectorAll('div.platforms > span.platform');\n\n  const gameTitle = titleElement ? titleElement.textContent.trim() : 'N/A';\n  const gameLink = linkElement ? `https://www.dekudeals.com${linkElement.getAttribute('href')}` : 'N/A';\n  const currentPrice = currentPriceElement ? currentPriceElement.textContent.trim() : 'N/A';\n  const originalPrice = originalPriceElement ? originalPriceElement.textContent.trim() : 'N/A';\n  const discount = discountElement ? discountElement.textContent.trim() : 'N/A';\n  const platforms = Array.from(platformElements).map(p => p.textContent.trim()).join(', ');\n\n  // Create a unique ID for the deal for tracking in SQLite\n  // Combination of title, platform, and current price should be fairly unique\n  const dealUniqueId = `${gameTitle}|${platforms}|${currentPrice}`;\n\n  deals.push({\n    json: {\n      dealUniqueId: dealUniqueId,\n      gameTitle: gameTitle,\n      gameLink: gameLink,\n      platforms: platforms,\n      currentPrice: currentPrice,\n      originalPrice: originalPrice,\n      discount: discount\n    }\n  });\n}\n\nreturn deals;"
      },
      "typeVersion": 1,
      "id": "--3"
    },
    {
      "name": "SQLite: テーブル存在確認",
      "type": "n8n-nodes-base.sqlite",
      "notes": {
        "text": "### 5. SQLite: Ensure Table Exists\n\nThis `SQLite` node ensures that a local database table named `notified_deals` exists. This table will store the unique IDs of deals you've already been notified about.\n\n**Setup:**\n* **Database:** `dekudeals` (this creates a file `dekudeals.db` in your n8n data directory).\n* **Query:** `CREATE TABLE IF NOT EXISTS notified_deals (...)` as shown.\n\n**No further action needed**; this node will run automatically. On subsequent runs, it will simply confirm the table exists.",
        "position": "right"
      },
      "position": [
        1200,
        220
      ],
      "parameters": {
        "query": "CREATE TABLE IF NOT EXISTS notified_deals (deal_id TEXT PRIMARY KEY, game_title TEXT, platforms TEXT, current_price TEXT, original_price TEXT, discount TEXT, deal_link TEXT, notified_date TEXT)",
        "database": "dekudeals"
      },
      "typeVersion": 1,
      "id": "SQLite--4"
    },
    {
      "name": "SQLite: 通知済み確認",
      "type": "n8n-nodes-base.sqlite",
      "notes": {
        "text": "### 6. SQLite: Check if Deal Already Notified\n\nThis `SQLite` node checks if each extracted deal (using its `dealUniqueId`) is already present in your `notified_deals` database.\n\n**Setup:**\n* **Database:** `dekudeals`\n* **Query:** `SELECT deal_id FROM notified_deals WHERE deal_id = '{{ $json.dealUniqueId }}'` (It looks for a match for the current deal's unique ID).\n\n**Output:** If the deal is found, this node will output an item. If not, it will output no item, which is crucial for the 'Item Lists' node below.",
        "position": "right"
      },
      "position": [
        1440,
        300
      ],
      "parameters": {
        "query": "SELECT deal_id FROM notified_deals WHERE deal_id = '{{ $json.dealUniqueId }}'",
        "database": "dekudeals"
      },
      "typeVersion": 1,
      "id": "SQLite--5"
    },
    {
      "name": "通知済み/新規に分割",
      "type": "n8n-nodes-base.itemLists",
      "notes": {
        "text": "### 7. Split into Notified/New\n\nThis `Item Lists` node takes the results from the 'SQLite: Check if Notified' node and splits them into two paths:\n* **Original items without a matching ID in the database:** These are **NEW** deals (`Output 1`).\n* **Original items with a matching ID in the database:** These are **ALREADY NOTIFIED** deals (`Output 2`).\n\n**No configuration needed**; it automatically separates the items based on whether the `SQLite` query found a match or not.",
        "position": "right"
      },
      "position": [
        1680,
        300
      ],
      "parameters": {
        "mode": "splitInBatches",
        "property": "dealUniqueId"
      },
      "typeVersion": 1,
      "id": "--6"
    },
    {
      "name": "条件分岐 (新規ディール発見時)",
      "type": "n8n-nodes-base.if",
      "notes": {
        "text": "### 8. If (New Deals Found)\n\nThis `If` node checks if there are any *new* deals (i.e., items coming from the 'New' path of the 'Split into Notified/New' node).\n\n* **'True' branch:** If new deals are found, the workflow proceeds to insert them into the database and send a notification.\n* **'False' branch:** If no new deals are found, the workflow ends here (no notification needed).\n\n**No configuration needed**; it automatically checks if there are any items.",
        "position": "right"
      },
      "position": [
        1920,
        220
      ],
      "parameters": {
        "conditions": [
          {
            "value1": "={{ $json.length }}",
            "value2": "0",
            "operation": "notEqual"
          }
        ]
      },
      "typeVersion": 1,
      "id": "--7"
    },
    {
      "name": "SQLite: 新規ディール挿入",
      "type": "n8n-nodes-base.sqlite",
      "notes": {
        "text": "### 9. SQLite: Insert New Deals\n\nThis `SQLite` node inserts the details of the newly found deals into your `notified_deals` database. This ensures you won't be notified about them again on subsequent runs.\n\n**Setup:**\n* **Database:** `dekudeals`\n* **Query:** The `INSERT INTO` query is pre-filled, saving all the extracted deal details.\n\n**No further action needed**; it automatically stores the new deal information.",
        "position": "right"
      },
      "position": [
        2160,
        140
      ],
      "parameters": {
        "query": "INSERT INTO notified_deals (deal_id, game_title, platforms, current_price, original_price, discount, deal_link, notified_date) VALUES ('{{ $json.dealUniqueId }}', '{{ $json.gameTitle }}', '{{ $json.platforms }}', '{{ $json.currentPrice }}', '{{ $json.originalPrice }}', '{{ $json.discount }}', '{{ $json.gameLink }}', '{{ new Date().toISOString() }}')",
        "database": "dekudeals"
      },
      "typeVersion": 1,
      "id": "SQLite--8"
    },
    {
      "name": "通知メッセージ整形",
      "type": "n8n-nodes-base.function",
      "notes": {
        "text": "### 10. Format Notification Message\n\nThis `Function` node takes the new deal details and formats them into a clear, readable message for your notification (e.g., email or Telegram).\n\n**Customization:**\n* You can change the introductory text, add more emojis, or adjust the display format of each deal. It dynamically adds 'Was' and 'Discount' info only if available.\n\n**No configuration needed if your property names match the previous node's output.**",
        "position": "right"
      },
      "position": [
        2400,
        140
      ],
      "parameters": {
        "options": {},
        "function": "let message = \"🎮 **New Game Deals on Deku Deals!** 🎮\\n\\n\";\n\nfor (const item of items) {\n  message += `**${item.json.gameTitle}** (${item.json.platforms})\\n` +\n             `  Current Price: **${item.json.currentPrice}**` +\n             (item.json.originalPrice !== 'N/A' ? ` (Was: ${item.json.originalPrice})` : '') +\n             (item.json.discount !== 'N/A' ? ` | Discount: ${item.json.discount}` : '') +\n             `\\n  Claim/View Deal: ${item.json.gameLink}\\n\\n`;\n}\n\nmessage += \"Check out all deals: https://www.dekudeals.com/most-popular\";\n\nreturn [{ json: { notificationMessage: message } }];"
      },
      "typeVersion": 1,
      "id": "--9"
    },
    {
      "name": "メール通知送信",
      "type": "n8n-nodes-base.gmail",
      "notes": {
        "text": "### 11. Send Email Notification\n\nThis `Gmail` node sends the formatted notification message about the new game deals.\n\n**Setup:**\n1.  **Gmail Credential:** Select your Gmail API credential.\n2.  **From Email:** Enter your Gmail address (must match the authenticated account).\n3.  **To Email:** **IMPORTANT: Change `YOUR_RECIPIENT_EMAIL@example.com` to your actual email address!**\n4.  **Subject & Text:** These fields pull the formatted message from the previous node.\n\n**To switch to Telegram/Slack/Discord:** Simply replace this node with your preferred notification service's node, and map the `notificationMessage` to its text field. You'll need to set up credentials for that service.",
        "position": "right"
      },
      "position": [
        2640,
        140
      ],
      "parameters": {
        "text": "={{ $json.notificationMessage }}",
        "options": {},
        "subject": "🎮 New Game Deals Alert! (Deku Deals)",
        "toEmail": "YOUR_RECIPIENT_EMAIL@example.com",
        "fromEmail": "YOUR_GMAIL_EMAIL@gmail.com"
      },
      "credentials": {
        "gmailApi": {
          "id": "YOUR_GMAIL_CREDENTIAL_ID",
          "resolve": false
        }
      },
      "typeVersion": 2,
      "id": "--10"
    }
  ],
  "pinData": {},
  "version": 1,
  "connections": {
    "-8--0": {
      "main": [
        [
          {
            "node": "Deku-Deals--1",
            "type": "main"
          }
        ]
      ]
    },
    "--7": {
      "main": [
        [
          {
            "node": "SQLite--8",
            "type": "main"
          }
        ],
        []
      ]
    },
    "Deku-Deals--1": {
      "main": [
        [
          {
            "node": "--2",
            "type": "main"
          }
        ]
      ]
    },
    "--2": {
      "main": [
        [
          {
            "node": "--3",
            "type": "main"
          }
        ]
      ]
    },
    "--6": {
      "main": [
        [
          {
            "node": "--7",
            "type": "main"
          }
        ],
        []
      ]
    },
    "SQLite--8": {
      "main": [
        [
          {
            "node": "--9",
            "type": "main"
          }
        ]
      ]
    },
    "SQLite--5": {
      "main": [
        [
          {
            "node": "--6",
            "type": "main"
          }
        ]
      ],
      "output": [
        {
          "type": "item",
          "toIndex": 0,
          "fromIndex": 0,
          "destination": [
            {
              "node": "通知済み/新規に分割",
              "input": "input2"
            }
          ]
        }
      ]
    },
    "--9": {
      "main": [
        [
          {
            "node": "--10",
            "type": "main"
          }
        ]
      ]
    },
    "SQLite--4": {
      "main": [
        [
          {
            "node": "SQLite--5",
            "type": "main"
          }
        ]
      ]
    },
    "--3": {
      "main": [
        [
          {
            "node": "SQLite--5",
            "type": "main"
          }
        ]
      ]
    }
  }
}
よくある質問

このワークフローの使い方は?

上記のJSON設定コードをコピーし、n8nインスタンスで新しいワークフローを作成して「JSONからインポート」を選択、設定を貼り付けて認証情報を必要に応じて変更してください。

このワークフローはどんな場面に適していますか?

中級 - 個人の生産性

有料ですか?

このワークフローは完全無料です。ただし、ワークフローで使用するサードパーティサービス(OpenAI APIなど)は別途料金が発生する場合があります。

ワークフロー情報
難易度
中級
ノード数11
カテゴリー1
ノードタイプ8
難易度説明

経験者向け、6-15ノードの中程度の複雑さのワークフロー

作成者
Piotr Sobolewski

Piotr Sobolewski

@piotrsobolewski

AI PhD with 7 years experience as a game dev CEO, currently teaching, helping others and building something new.

外部リンク
n8n.ioで表示

このワークフローを共有

カテゴリー

カテゴリー: 34