Automatisierter Tracking von Spielangeboten auf mehreren Plattformen
Dies ist ein Personal Productivity-Bereich Automatisierungsworkflow mit 11 Nodes. Hauptsächlich werden If, Cron, Gmail, Sqlite, Function und andere Nodes verwendet. Automatisches Verfolgen von Plattformübergreifenden Spielangeboten mit Deku Deals und Gmail-Erinnerungen
- •Google-Konto + Gmail API-Anmeldedaten
- •Möglicherweise sind Ziel-API-Anmeldedaten erforderlich
Verwendete Nodes (11)
Kategorie
{
"nodes": [
{
"name": "Tägliche Prüfung (8 Uhr)",
"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": "T-gliche-Pr-fung-8-Uhr--0"
},
{
"name": "Deku Deals-Seite abrufen",
"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-Seite-abrufen-1"
},
{
"name": "Jede Angebotskarte extrahieren",
"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": "Jede-Angebotskarte-extrahieren-2"
},
{
"name": "Angebotsdetails parsen (Funktionscode)",
"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": "Angebotsdetails-parsen-Funktionscode--3"
},
{
"name": "SQLite: Tabelle sicherstellen",
"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-Tabelle-sicherstellen-4"
},
{
"name": "SQLite: Prüfen, ob benachrichtigt",
"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-Pr-fen-ob-benachrichtigt-5"
},
{
"name": "In benachrichtigt/neu aufteilen",
"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": "In-benachrichtigt-neu-aufteilen-6"
},
{
"name": "Wenn (neue Angebote gefunden)",
"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": "Wenn-neue-Angebote-gefunden--7"
},
{
"name": "SQLite: Neue Angebote einfügen",
"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-Neue-Angebote-einf-gen-8"
},
{
"name": "Benachrichtigungsnachricht formatieren",
"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": "Benachrichtigungsnachricht-formatieren-9"
},
{
"name": "E-Mail-Benachrichtigung senden",
"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": "E-Mail-Benachrichtigung-senden-10"
}
],
"pinData": {},
"version": 1,
"connections": {
"T-gliche-Pr-fung-8-Uhr--0": {
"main": [
[
{
"node": "Deku-Deals-Seite-abrufen-1",
"type": "main"
}
]
]
},
"Wenn-neue-Angebote-gefunden--7": {
"main": [
[
{
"node": "SQLite-Neue-Angebote-einf-gen-8",
"type": "main"
}
],
[]
]
},
"Deku-Deals-Seite-abrufen-1": {
"main": [
[
{
"node": "Jede-Angebotskarte-extrahieren-2",
"type": "main"
}
]
]
},
"Jede-Angebotskarte-extrahieren-2": {
"main": [
[
{
"node": "Angebotsdetails-parsen-Funktionscode--3",
"type": "main"
}
]
]
},
"In-benachrichtigt-neu-aufteilen-6": {
"main": [
[
{
"node": "Wenn-neue-Angebote-gefunden--7",
"type": "main"
}
],
[]
]
},
"SQLite-Neue-Angebote-einf-gen-8": {
"main": [
[
{
"node": "Benachrichtigungsnachricht-formatieren-9",
"type": "main"
}
]
]
},
"SQLite-Pr-fen-ob-benachrichtigt-5": {
"main": [
[
{
"node": "In-benachrichtigt-neu-aufteilen-6",
"type": "main"
}
]
],
"output": [
{
"type": "item",
"toIndex": 0,
"fromIndex": 0,
"destination": [
{
"node": "In benachrichtigt/neu aufteilen",
"input": "input2"
}
]
}
]
},
"Benachrichtigungsnachricht-formatieren-9": {
"main": [
[
{
"node": "E-Mail-Benachrichtigung-senden-10",
"type": "main"
}
]
]
},
"SQLite-Tabelle-sicherstellen-4": {
"main": [
[
{
"node": "SQLite-Pr-fen-ob-benachrichtigt-5",
"type": "main"
}
]
]
},
"Angebotsdetails-parsen-Funktionscode--3": {
"main": [
[
{
"node": "SQLite-Pr-fen-ob-benachrichtigt-5",
"type": "main"
}
]
]
}
}
}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?
Fortgeschritten - Persönliche Produktivität
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.
Verwandte Workflows
Piotr Sobolewski
@piotrsobolewskiAI PhD with 7 years experience as a game dev CEO, currently teaching, helping others and building something new.
Diesen Workflow teilen