{ "name": "Book Review", "nodes": [ { "parameters": { "rule": { "interval": [ { "triggerAtHour": 19 } ] } }, "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.3, "position": [ 0, -192 ], "id": "f0c86dde-aa19-4440-b17c-c572b582da5e", "name": "Schedule Trigger" }, { "parameters": { "method": "POST", "url": "https://api.hardcover.app/v1/graphql", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpBearerAuth", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "content-type", "value": "application/json" } ] }, "sendBody": true, "bodyParameters": { "parameters": [ { "name": "query", "value": "query GetFinishedBooks { me { user_books(where: {status_id: {_eq: 3}}, limit: 5) { book { id title contributions { author { name } } images { url } } last_read_date updated_at } } }" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 224, -192 ], "id": "e5c28f64-29ed-40ae-804e-896c10f3bc58", "name": "HTTP Request", "credentials": { "httpBearerAuth": { "id": "Kmf2fBCFkuRuWWZa", "name": "Hardcover" } } }, { "parameters": { "jsCode": "const responseData = $input.first().json;\nconst meData = responseData?.data?.me;\nconst userBooks =\n (Array.isArray(meData) && meData[0]?.user_books) || meData?.user_books || [];\n\nconst newBooks = [];\n\nfor (const ub of userBooks) {\n const check = await this.helpers.httpRequest({\n method: \"GET\",\n url:\n \"https://cms.dk0.dev/items/book_reviews?filter[hardcover_id][_eq]=\" +\n ub.book.id +\n \"&fields=id,translations.id&limit=1\",\n headers: {\n Authorization: \"Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB\",\n },\n });\n\n const existing = check.data?.[0];\n const hasReview =\n existing && existing.translations && existing.translations.length > 0;\n\n if (!hasReview) {\n newBooks.push({\n json: {\n hardcover_id: String(ub.book.id),\n directus_id: existing ? existing.id : null,\n title: ub.book.title,\n author: ub.book.contributions?.[0]?.author?.name ?? \"Unknown\",\n image: ub.book.images?.[0]?.url ?? null,\n finished_at: ub.last_read_date ?? ub.updated_at ?? null,\n already_in_directus: !!existing,\n },\n });\n }\n}\n\nreturn newBooks.length > 0 ? newBooks[0] : [{ json: { skip: true } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 448, -192 ], "id": "60380362-e954-40ee-b0d0-7bc1edbaf9d3", "name": "Filter books" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "b356ade3-5cf0-40dd-bb47-e977f354e803", "leftValue": "={{ $json.skip }}", "rightValue": "={{ $json.skip }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 672, -192 ], "id": "45f65c65-ae6a-46b0-9d96-46f0a32e59db", "name": "If" }, { "parameters": { "jsCode": "const book = $input.first().json;\nif (book.skip) return [{ json: { skip: true } }];\n\nconst parts = [];\nparts.push(\"Du hilfst jemandem eine Buchbewertung zu schreiben.\");\nparts.push(\"Das Buch ist \" + book.title + \" von \" + book.author + \".\");\nparts.push(\"Erstelle 4 kurze spezifische Fragen zum Buch.\");\nparts.push(\"Die Fragen sollen helfen eine Review zu schreiben.\");\nparts.push(\"Frage auf Deutsch.\");\nparts.push(\"Antworte NUR als JSON Array mit 4 Strings. Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche (–, —, -).\");\nconst prompt = parts.join(\" \");\n\nconst aiResponse = await this.helpers.httpRequest({\n method: \"POST\",\n url: \"https://openrouter.ai/api/v1/chat/completions\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \"Bearer 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\",\n },\n body: {\n model: \"openrouter/free\",\n messages: [{ role: \"user\", content: prompt }],\n },\n});\n\nconst aiText = aiResponse.choices?.[0]?.message?.content ?? \"[]\";\nconst match = aiText.match(/\\[[\\s\\S]*\\]/);\n\nconst f1 = \"Wie hat dir das Buch gefallen?\";\nconst f2 = \"Was war der beste Teil?\";\nconst f3 = \"Was hast du mitgenommen?\";\nconst f4 = \"Wem empfiehlst du es?\";\nconst fallback = [f1, f2, f3, f4];\n\nconst questions = match ? JSON.parse(match[0]) : fallback;\n\nreturn [{ json: { ...book, questions } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 896, -192 ], "id": "b56ab681-90d8-4376-9408-dc3302ab55bd", "name": "ai" }, { "parameters": { "chatId": "145931600", "text": "={{ '📚 ' + $json.title + ' von ' + $json.author + '\\n\\nBeantworte bitte:\\n\\n1. ' + $json.questions[0] + '\\n2. ' + $json.questions[1] + '\\n3. ' + $json.questions[2] + '\\n4. ' + $json.questions[3] + '\\n\\n⭐ Bewertung (1-5)?\\n\\nAntworte so (kopiere und ergänze):\\n\\n/review' + $json.hardcover_id + ' Hier deine Antworten als Text' }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 1136, -208 ], "id": "13087afe-8a1d-457f-a1f1-e0aa64fc0e26", "name": "Send a text message", "webhookId": "eaa44b55-b3b1-4747-9b6a-dfc920910b4b", "credentials": { "telegramApi": { "id": "ADurvy9EKUDzbDdq", "name": "DK0_Server" } } } ], "pinData": {}, "connections": { "Schedule Trigger": { "main": [ [ { "node": "HTTP Request", "type": "main", "index": 0 } ] ] }, "HTTP Request": { "main": [ [ { "node": "Filter books", "type": "main", "index": 0 } ] ] }, "Filter books": { "main": [ [ { "node": "If", "type": "main", "index": 0 } ] ] }, "If": { "main": [ [], [ { "node": "ai", "type": "main", "index": 0 } ] ] }, "ai": { "main": [ [ { "node": "Send a text message", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": false }, "versionId": "4c605d70-0428-4611-9ad8-d9452c2660a7", "meta": { "templateCredsSetupCompleted": true, "instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d" }, "id": "FDQ5Qmk9POy4Ajdd", "tags": [] }