Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/aliilhangis_n8n-skill-claude.json
T

1 line
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "aliilhangis/n8n-skill-claude", "files": {"SKILL.md": {"799f08c269e10e2fd17da8c4c4990bff25b1bf80": {"t": 1785674437.7606294, "n": 16340, "h": "d5367186455971c1", "c": "# n8n Workflow Automation Skill\n**by @thealiilhan**\n\nBu skill, n8n ile production-ready otomasyon workflow'ları oluşturmak için gereken tüm pattern'leri, node kurallarını ve entegrasyon detaylarını içerir.\n\n---\n\n## TEMEL EXPRESSION SYNTAX\n\n```\n{{ $json.fieldName }} → mevcut node çıktısı\n{{ $json['field name'] }} → boşluklu alan adı\n{{ $('NodeAdı').item.json.fieldName }} → başka node'dan veri\n{{ $('NodeAdı').all()[0].json.fieldName }} → başka node'dan ilk item\n{{ $node[\"NodeAdı\"].json.fieldName }} → alternatif syntax\n{{ JSON.stringify($json.array) }} → array'i JSON string'e çevir\n{{ $now.toISO() }} → şu anki timestamp (Luxon)\n{{ $today.toFormat('yyyy-MM-dd') }} → tarih formatı\n{{ $json.list.length }} → array uzunluğu\n{{ $runIndex }} → SplitInBatches loop index\n```\n\n**Kritik:** `$('NodeAdı')` büyük/küçük harf duyarlıdır. Türkçe karakter dahil, node adı değişirse expression da güncellenmeli.\n\n---\n\n## CODE NODE — JAVASCRIPT\n\n```javascript\n// Tüm input item'larını al\nconst items = $input.all();\n\n// Tek item\nconst item = $input.first();\n\n// Başka node'dan veri al\nconst config = $('Config').item.json;\nconst allRows = $('Apify Sonuçları').all();\n\n// Temel output formatı — her zaman { json: {} } döndür\nreturn items.map(item => ({\n json: {\n fieldA: item.json.something,\n fieldB: 'statik değer',\n processedAt: new Date().toISOString()\n }\n}));\n\n// Filtreleme\nconst filtered = items.filter(item => item.json.rating <= 2);\nreturn filtered.map(item => ({ json: item.json }));\n\n// Birden fazla item üret (split/expand)\nconst result = [];\nfor (const item of items) {\n for (const row of item.json.rows || []) {\n result.push({ json: row });\n }\n}\nreturn result;\n\n// Boş kontrol — hiç output vermemek için\nif (!items.length) return [];\n```\n\n### HTTP Request (Code Node içinden)\n```javascript\nconst response = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.example.com/endpoint',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer TOKEN'\n },\n body: { key: 'value' },\n json: true // body'yi otomatik stringify, response'u parse et\n});\nreturn [{ json: response }];\n```\n\n### JSON Parse (güvenli)\n```javascript\nlet parsed;\ntry {\n parsed = JSON.parse(rawText);\n} catch (e) {\n const cleaned = rawText\n .replace(/```json\\n?/g, '')\n .replace(/```\\n?/g, '')\n .trim();\n parsed = JSON.parse(cleaned);\n}\n```\n\n---\n\n## SUPABASE ENTEGRASYONU\n\n### HTTP Request Node ile (önerilen)\n```\nMethod: POST\nURL: https://PROJE_ID.supabase.co/rest/v1/tablo_adi\nHeaders:\n apikey: SERVICE_ROLE_KEY\n Authorization: Bearer SERVICE_ROLE_KEY\n Content-Type: application/json\n Prefer: return=representation → insert sonrası veriyi döndür\n Prefer: resolution=merge-duplicates → upsert (üsttekiyle birlikte)\nBody: {{ $json }}\n```\n\n### Code Node içinden (batch insert)\n```javascript\nconst rows = $input.all().map(item => ({\n field1: item.json.value1,\n field2: item.json.value2\n}));\n\nconst response = await this.helpers.httpRequest({\n method: 'POST',\n url: `https://${PROJECT_ID}.supabase.co/rest/v1/${TABLE}`,\n headers: {\n 'apikey': SERVICE_ROLE_KEY,\n 'Authorization': `Bearer ${SERVICE_ROLE_KEY}`,\n 'Content-Type': 'application/json',\n 'Prefer': 'return=minimal'\n },\n body: rows,\n json: true\n});\nreturn [{ json: { inserted: rows.length } }];\n```\n\n### SELECT / Filtreleme\n```\nGET https://PROJE.supabase.co/rest/v1/tablo?select=*&status=eq.active&limit=100\nGET https://PROJE.supabase.co/rest/v1/tablo?select=id,name&created_at=gte.2024-01-01\nGET https://PROJE.supabase.co/rest/v1/tablo?select=*&order=created_at.desc&limit=50\n```\n\n**Kritik:** RLS açıksa anon key çalışmaz — mutlaka **service role key** kullan.\n\n---\n\n## APIFY ASYNC POLLING PATTERN\n\nApify actor'ları asenkron çalışır. Run başlatılır, bitene kadar status poll edilir, sonra dataset çekilir.\n\n```\nRun Başlat → Run ID Kaydet → Wait → Status Kontrol → Poll State Güncelle\n ↓\n SUCCEEDED? → YES → Dataset Çek\n → NO → Max Poll Aşıldı?\n → YES → Hata log\n → NO → Wait'e dön (loop)\n```\n\n### Adım 1 — Run Başlat (HTTP Request)\n```\nPOST https://api.apify.com/v2/acts/ACTOR_ID/runs?token=APIFY_TOKEN\nContent-Type: application/json\nBody: { ...actor input parametreleri }\n```\n\n### Adım 2 — Run ID Kaydet (Set Node)\n```\nrunId = {{ $json.data.id }}\npollCount = 0\nmaxPolls = 20\n```\n\n### Adım 3 — Wait Node\n```\n15–30 saniye (actor ağırlığına göre ayarla)\nNOT: Wait node bir webhookId alır. Node'u silip yeniden eklersen ID değişir, loop kırılır.\n```\n\n### Adım 4 — Status Kontrol (HTTP Request)\n```\nGET https://api.apify.com/v2/acts/ACTOR_ID/runs/{{ $('Run ID Kaydet').item.json.runId }}?token=APIFY_TOKEN\n```\n\n### Adım 5 — Poll State Güncelle (Set Node)\n```\nrunId = {{ $('Run ID Kaydet').item.json.runId }}\npollCount = {{ $('Run ID Kaydet').item.json.pollCount + 1 }}\nmaxPolls = {{ $('Run ID Kaydet').item.json.maxPolls }}\nstatus = {{ $json.data.status }}\n```\n\n### Adım 6 — IF: SUCCEEDED mi?\n```\nleftValue: {{ $json.status }}\noperation: equals\nrightValue: SUCCEEDED\nTRUE → Dataset Çek\nFALSE → Max Poll Aşıldı mı? (pollCount >= maxPolls)\n```\n\n### Adım 7 — Dataset Çek (HTTP Request)\n```\nGET https://api.apify.com/v2/actor-runs/{{ $('Run ID Kaydet').item.json.runId }}/dataset/items\nParams: token=APIFY_TOKEN, format=json, clean=true\n```\n\n### Alternatif: run-sync (kısa actor'lar için)\n```\nPOST https://api.apify.com/v2/acts/ACTOR_ID/run-sync-get-dataset-items?token=TOKEN&waitForFinish=120\n```\n2 dakika üzeri işlerde timeout'a düşer → o zaman async pattern kullan.\n\n---\n\n## SOSYAL MEDYA SCRAPING\n\n### Instagram (RapidAPI)\n```\n# Profil\nGET https://instagram-social-api.p.rapidapi.com/v1/info?username_or_id_or_url=USERNAME\n\n# Postlar\nGET https://instagram-social-api.p.rapidapi.com/v1/posts?username_or_id_or_url=USERNAME\n\nHeaders: X-RapidAPI-Key, X-RapidAPI-Host: instagram-social-api.p.rapidapi.com\n\n# Veri yapısı:\n# response.data.items[] → postlar\n# item.caption.text → caption\n# item.caption.hashtags[] → hashtagler\n# item.location.name → lokasyon\n# item.like_count, item.comment_count\n```\n\n### TikTok (RapidAPI — keyword arama)\n```\nGET https://tiktok-scraper7.p.rapidapi.com/feed/search\nParams:\n keywords: arama terimi\n region: tr\n count: 10\n cursor: 0\n publish_time: 0 (tümü) | 1 (gün) | 7 (hafta) | 30 (ay)\n sort_type: 0 (alaka) | 1 (beğeni)\nHeaders: X-Rapidapi-Key, X-Rapidapi-Host: tiktok-scraper7.p.rapidapi.com\n```\n\n### Twitter/X (Apify — run-sync)\n```\nPOST https://api.apify.com/v2/acts/61RPP7dywgiy0JPD0/run-sync-get-dataset-items?token=TOKEN\nBody:\n{\n \"searchTerms\": [\"arama terimi\"],\n \"maxItems\": 100,\n \"sort\": \"Latest\",\n \"tweetLanguage\": \"tr\",\n \"onlyVerifiedUsers\": false,\n \"onlyTwitterBlue\": false\n}\nTimeout: 300000ms\n\n# Veri yapısı:\n# item.id, item.text, item.createdAt, item.url\n# item.author.userName, item.author.name, item.author.followers\n# item.author.isVerified, item.author.isBlueVerified, item.author.location\n# item.likeCount, item.retweetCount, item.replyCount, item.viewCount\n```\n\n### Trustpilot (Apify — async)\n```\nActor ID: 6q70QEFc2Zk0ObldU\nBody:\n{\n \"companyUrls\": [\"domain.com\"],\n \"maxReviewsPerCompany\": 10,\n \"sort\": \"recency\",\n \"verified\": false,\n \"withReplies\": false,\n \"includeCompanyInfo\": true\n}\n→ Async polling pattern zorunlu (2+ dakika sürer)\n```\n\n### YouTube (RapidAPI)\n```\nGET https://youtube-search-and-download.p.rapidapi.com/search\nParams: query, type=v, sort=r (relevance) | u (upload date)\nHeaders: X-RapidAPI-Key, X-RapidAPI-Host: youtube-search-and-download.p.rapidapi.com\n```\n\n### Meta Ads Library / Google Ads Transparency\n```\n→ Apify actor ile çek, async pattern uygula\n→ Veri: advertiser, ad copy, start date, platforms, media\n```\n\n---\n\n## VERİ DEPOLAMA\n\n### Google Sheets — Append\n```\nNode: Google Sheets → Operation: Append\ndocumentId: Sheet URL veya ID\nsheetName: Sayfa adı\nColumns mappingMode: autoMapInputData\n\n# Kritik: JSON key adları = kolon başlığı (büyük/küçük harf dahil eşleşmeli)\nreturn items.map(item => ({\n json: {\n 'Tweet ID': item.json.id,\n 'Kullanıcı': item.json.author?.userName || '',\n 'Beğeni': item.json.likeCount || 0,\n 'Tarih': item.json.createdAt,\n 'Çekilme Zamanı': new Date().toISOString()\n }\n}));\n```\n\n### Google Sheets — Upsert\n```\nNode: Google Sheets → Operation: appendOrUpdate\nmatchingColumns: ['Tweet ID'] → bu kolona göre güncelle, yoksa ekle\n```\n\n### Supabase — Upsert (duplicate önleme)\n```\nHeaders'a ekle: Prefer: resolution=merge-duplicates\n+ on_conflict parametresi için URL'e ekle: ?on_conflict=unique_kolon\n```\n\n---\n\n## EMAIL\n\n### Brevo ile HTML Mail\n```\nPOST https://api.brevo.com/v3/smtp/email\nHeaders: api-key: BREVO_KEY, Content-Type: application/json\nBody:\n{\n \"sender\": { \"name\": \"Bot Adı\", \"email\": \"gonderici@domain.com\" },\n \"to\": [{ \"email\": \"{{ $json.recipientEmail }}\", \"name\": \"{{ $json.recipientName }}\" }],\n \"cc\": [{ \"email\": \"{{ $json.ccEmail }}\" }],\n \"subject\": \"{{ $json.subject }}\",\n \"htmlContent\": \"{{ $json.htmlContent }}\"\n}\nTimeout: 30000ms\n```\n\n### HTML Mail Şablonu\n```javascript\nconst htmlContent = `\n<!DOCTYPE html><html>\n<body style=\"background:#f3f4f6; padding:40px 10px; margin:0;\">\n <div style=\"max-width:650px; margin:0 auto; background:#fff; border-radius:12px;\n box-shadow:0 4px 20px rgba(0,0,0,0.08); border:1px solid #e5e7eb; overflow:hidden;\">\n <div style=\"background:linear-gradient(135deg,#4A90E2 0%,#6366f1 100%);\n padding:35px 20px; text-align:center; color:white;\">\n <h1 style=\"margin:0; font-size:24px; font-family:sans-serif;\">${title}</h1>\n <p style=\"margin-top:8px; opacity:0.9; font-size:14px; font-family:sans-serif;\">${date}</p>\n </div>\n <div style=\"padding:30px 40px;\">${body}</div>\n <div style=\"background:#f9fafb; padding:20px; text-align:center; border-top:1px solid #e5e7eb;\">\n <p style=\"margin:0; color:#9ca3af; font-size:12px; font-family:sans-serif;\">Otomatik rapor</p>\n </div>\n </div>\n</body></html>`;\n```\n\n### Gmail (n8n Native)\n```\nNode: Gmail → Operation: Send\nTo: {{ $json.email }}\nSubject: {{ $json.subject }}\nMessage: {{ $json.htmlContent }}\nMessage Type: HTML\n```\n\n---\n\n## AI ENTEGRASYONU\n\n### OpenAI (HTTP Request Node)\n```\nPOST https://api.openai.com/v1/chat/completions\nHeaders: Authorization: Bearer sk-..., Content-Type: application/json\nBody:\n{\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n { \"role\": \"system\", \"content\": \"Sen bir uzman asistansın.\" },\n { \"role\": \"user\", \"content\": {{ JSON.stringify($json.prompt) }} }\n ],\n \"response_format\": { \"type\": \"json_object\" },\n \"temperature\": 0.7,\n \"max_tokens\": 2000\n}\n\n# Yanıtı oku: {{ $json.choices[0].message.content }}\n```\n\n### n8n AI Node (LangChain)\n```\nNode: @n8n/n8n-nodes-langchain.openAi\nmodelId: gpt-4o-mini\nresponses → content: {{ $json.prompt }}\n\n# Çıktıyı oku: {{ $json.output[0].content[0].text }}\n```\n\n### Prompt İçinde n8n Değişkeni\n```javascript\n// String içinde\n`Şu kullanıcıyı analiz et: ${$('Config').item.json.username}`\n\n// Expression içinde — tırnak sorununu önlemek için JSON.stringify kullan\n{{ JSON.stringify($json.data) }}\n\n// Sistem promptu + veri birleştirme\nconst prompt = `Veriyi işle:\\n${JSON.stringify(items.map(i => i.json))}`;\n```\n\n---\n\n## WORKFLOW MİMARİSİ\n\n### Config Node Pattern\nHer workflow'un başına bir Set node koy, tüm değişkenleri burada tut:\n```\nNode: Set (adı: Config)\n companyUrls = \"domain1.com,domain2.com\"\n maxResults = 5\n targetEmail = \"hedef@email.com\"\n slackWebhook = \"https://hooks.slack.com/...\"\n```\nKullanımı: `{{ $('Config').item.json.maxResults }}`\n\n### Multi-Platform Paralel Akış\n```\nTrigger → Config\n ↓\n ┌─────────┼──────────┬──────────┐\n Platform1 Platform2 Platform3 Platform4\n ↓ ↓ ↓ ↓\n Format Format Format Format\n ↓ ↓ ↓ ↓\n Hedef Hedef Hedef Hedef\n```\nHer dal bağımsız çalışır, birinin hatası diğerini etkilemez.\n\n### Split → Process → Merge\n```javascript\n// Büyük array'i item'lara böl\nreturn bigArray.map(row => ({ json: row }));\n\n// Sonra SplitInBatches node ile batch'le (rate limit varsa)\n// Batchsize: 10, her batch arasında Wait: 1sn\n\n// Merge node ile sonunda birleştir\n// Mode: Append All Items\n```\n\n### Website Monitor Pattern\n```\nSchedule Trigger (12 saatte bir)\n → URL Listesi (Code Node) [array → her URL ayrı item]\n → HTTP Request (onError: continueErrorOutput)\n ├── [Başarı output] → IF status == \"up\" → log\n └── [Hata output] → Email / Slack alert\n```\n\n---\n\n## HATA YÖNETİMİ\n\n### HTTP Request Node\n```\nSettings → On Error: continueErrorOutput\n → output 1: başarılı yanıt\n → output 2: hata ($json.error.message)\n\nSettings → Retry On Fail: true, Max Tries: 2, Wait: 1000ms\nSettings → Timeout: 300000 (scraping işlemlerinde)\n```\n\n### Code Node — Try/Catch\n```javascript\ntry {\n const result = JSON.parse($json.responseText);\n return [{ json: { success: true, data: result } }];\n} catch (error) {\n return [{ json: { success: false, error: error.message, raw: $json.responseText } }];\n}\n```\n\n### Hata Email/Slack Bildirimi\n```javascript\n// Hata branch'inde:\nreturn [{\n json: {\n subject: `⚠️ Workflow Hatası - ${new Date().toLocaleDateString('tr-TR')}`,\n body: `Node: ${$json.node || 'bilinmiyor'}\\nHata: ${$json.error?.message || 'bilinmiyor'}`,\n recipient: 'ali@domain.com'\n }\n}];\n```\n\n---\n\n## TRIGGERLAR\n\n```\n# Zamanlama\nSchedule Trigger\n field: minutes (minutesInterval: 30)\n field: hours (hoursInterval: 6)\n field: days (hour: 9, minute: 0) → her sabah 09:00\n field: weeks → haftalık\n\n# Manuel test\nManual Trigger\n\n# Webhook (dışarıdan tetikleme)\nWebhook Trigger\n HTTP Method: POST\n URL: https://n8n-instance/webhook/UUID\n Authentication: Header Auth\n\n# Form\nn8n Form Trigger → form alanlarını tanımla\n URL: https://n8n-instance/form/UUID\n```\n\n---\n\n## CREDENTIAL TIPLERI\n\n| Servis | n8n Credential Tipi | Detay |\n|---|---|---|\n| Apify | httpQueryAuth | `?token=TOKEN` query param |\n| Apify | httpHeaderAuth | `Authorization: Bearer TOKEN` |\n| RapidAPI | httpHeaderAuth | `X-RapidAPI-Key` header |\n| Supabase | httpHeaderAuth | `apikey` + `Authorization: Bearer` |\n| OpenAI | httpHeaderAuth | `Authorization: Bearer sk-...` |\n| Brevo | httpHeaderAuth | `api-key` header |\n| Google Sheets | googleSheetsOAuth2Api | OAuth2 |\n| Gmail | gmailOAuth2 | OAuth2 |\n| GitHub | githubApi | Personal access token |\n| Slack | slackApi | Bot token |\n\n---\n\n## SIK YAPILAN HATALAR\n\n```\n1. $('NodeAdı') — node adı değişti, expression güncellenmedi → kırmızı hata\n2. Supabase anon key ile RLS bypass → 401/403, service role key kullan\n3. Apify run-sync timeout → 2dk+ işlerde async pattern'e geç\n4. Code node'da return eksik veya yanlış format → null/undefined output\n5. Google Sheets autoMapInputData — key adı kolon başlığıyla eşleşmiyor → boş satır\n6. Wait node loop'ta silinip yeniden eklendi → webhookId değişir, loop kırılır\n7. Array'i expression'a direkt koymak → [object Object], JSON.stringify kullan\n8. OpenAI prompt'unda çift tırnak içinde çift tırnak → expression bozulur\n9. Supabase batch insert'te rows boş array → 400 hatası, önce kontrol et\n10. Schedule Trigger timezone — n8n instance UTC'de, Türkiye UTC+3\n11. continueOnFail ≠ continueErrorOutput — ikincisi hata branch'i açar, birincisi açmaz\n12. SplitInBatches loop'ta $('önceki node') — loop index'ine göre değişir, dikkatli kullan\n```\n\n---\n\n## ÖNEMLİ NOTLAR\n\n- Code node çıktısı her zaman `[{ json: {...} }]` formatında olmalı\n- n8n expression'larında Luxon (tarih), lodash ve temel JS kullanılabilir\n- HTTP Request node'da JSON body gönderirken `Content-Type: application/json` header'ı ekle\n- `continueOnFail` ile `continueErrorOutput` farklı davranır — hata yönetiminde ikincisini tercih et\n- Supabase'de `Prefer: return=representation` insert sonrası eklenen satırı döndürür\n- Apify dataset URL'inde `clean=true` boş/null kayıtları filtreler\n- n8n Cloud'da timeout 1 dakika, self-hosted'da ayarlanabilir\n"}}}}