This workflow follows the Google Drive → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "My workflow",
"nodes": [
{
"parameters": {
"jsCode": "// \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u0441\u0435 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\nconst items = $input.all();\nconst result = [];\n\n// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u044b \u0432 YYYY-MM-DD\nfunction formatDate(date) {\n const d = new Date(date);\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\u0430 (+3 \u0434\u043d\u044f)\nfunction getDeadline() {\n const date = new Date();\n date.setDate(date.getDate() + 3);\n return formatDate(date);\n}\n\n// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f employment_type\nfunction getEmploymentType(employment) {\n const employmentMap = {\n 'full': 'fulltime',\n 'part': 'parttime',\n 'intern': 'intern',\n '\u0441\u0442\u0430\u0436\u0438\u0440\u043e\u0432\u043a\u0430': 'intern',\n 'project': 'fulltime',\n 'volunteer': 'intern'\n };\n const empLower = (employment || '').toLowerCase();\n return employmentMap[empLower] || 'fulltime';\n}\n\n// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\nfunction getSource(source) {\n if (source === 'hh.ru') return 'hh';\n if (source === '\u0425\u0430\u0431\u0440 \u041a\u0430\u0440\u044c\u0435\u0440\u0430') return 'habr';\n return source || 'unknown';\n}\n\n// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\nfunction getMatchReason(title, content, rating) {\n const reasons = [];\n const titleLower = (title || '').toLowerCase();\n const contentLower = (content || '').toLowerCase();\n \n const keywords = {\n 'AI/\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438': ['\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', '\u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442', 'gpt', 'ai'],\n 'no-code/low-code': ['no-code', 'nocode', 'low-code', 'lowcode'],\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f': ['\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446', '\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442', 'robotics'],\n '\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u043c\u0438': ['project', 'pm', 'agile', 'scrum', '\u043a\u0430\u043d\u0431\u0430\u043d', 'waterfall'],\n '\u043f\u043e\u0437\u0438\u0446\u0438\u044f (\u0441\u0442\u0430\u0436\u0451\u0440/\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a)': ['\u0441\u0442\u0430\u0436\u0435\u0440', '\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a', '\u043c\u043b\u0430\u0434\u0448\u0438\u0439', 'junior', 'trainee', '\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442']\n };\n \n for (const [category, words] of Object.entries(keywords)) {\n for (const word of words) {\n if (titleLower.includes(word)) {\n reasons.push(`${category} (+3 \u0431\u0430\u043b\u043b\u0430 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438)`);\n break;\n } else if (contentLower.includes(word)) {\n reasons.push(`${category} (+1 \u0431\u0430\u043b\u043b \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438)`);\n break;\n }\n }\n }\n \n if (reasons.length === 0) {\n return `\u0421\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439 \u043f\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u043c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u0420\u0435\u0439\u0442\u0438\u043d\u0433: ${rating}`;\n }\n \n return `\u0420\u0435\u0439\u0442\u0438\u043d\u0433: ${rating}\\n\\n\u2705 ${reasons.slice(0, 3).join('\\n\u2705 ')}`;\n}\n\n// \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0436\u0434\u0443\u044e \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u044e\nfor (const item of items) {\n const v = item.json;\n const createdAt = formatDate(new Date());\n const deadline = getDeadline();\n \n // \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430: \u0412\u0430\u043a\u0430\u043d\u0441\u0438\u044f - \u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f - \u0414\u0414-\u041c\u041c-\u0413\u0413\u0413\u0413.md\n const safeRole = (v.title || '\u0412\u0430\u043a\u0430\u043d\u0441\u0438\u044f')\n .replace(/[<>:\"/\\\\|?*]/g, '') // \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b\n .substring(0, 50);\n const safeCompany = (v.author || '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f')\n .replace(/[<>:\"/\\\\|?*]/g, '')\n .substring(0, 30);\n const dateStr = createdAt.split('-').reverse().join('-'); // 2025-04-12 \u2192 12-04-2025\n const fileName = `${safeRole} - ${safeCompany} - ${dateStr}.md`;\n \n // \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0437\u0430\u043c\u0435\u0442\u043a\u0438\n const noteContent = `---\ncompany: ${safeCompany}\nrole: ${safeRole}\nstatus: \u0432_\u0430\u043d\u0430\u043b\u0438\u0437\u0435\nsalary: ${v.salary || ''}\nlink: ${v.link || ''}\nexternal_id: ${v.id || v.external_id || ''}\ndeadline: ${deadline}\nsource: ${getSource(v.source)}\nlocation: ${v.city || ''}\nemployment_type: ${getEmploymentType(v.employment_type)}\nparsedByBot: false\nmatchScore: ${v.rating || 0}\ntags: vacancy\ncreatedAt: ${createdAt}\n---\n\n**\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439:**\n- [ ] \u0420\u0430\u0437\u0431\u043e\u0440 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u043d?\n- **\u0414\u0430\u0442\u0430 \u043e\u0442\u043a\u043b\u0438\u043a\u0430:** \n\n## \u041f\u043e\u0447\u0435\u043c\u0443 \u0431\u043e\u0442 \u0432\u044b\u0431\u0440\u0430\u043b \u044d\u0442\u0443 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u044e\n\n${getMatchReason(v.title, v.content, v.rating || 0)}\n\n**\u0421\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u043c\u0435\u0442\u043a\u0438:**\n- [[\u0414\u0430\u0448\u0431\u043e\u0440\u0434 - \u0412\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u043f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0443]]\n- [[\u0421\u043f\u0438\u0441\u043e\u043a \u0440\u0435\u0437\u044e\u043c\u0435]]\n`;\n \n result.push({\n json: {\n fileName: fileName,\n content: noteContent,\n filePath: `Career-project-v1.0/02 - \u041f\u041e\u0418\u0421\u041a \u0420\u0410\u0411\u041e\u0422\u042b/01 - \u0412\u0430\u043a\u0430\u043d\u0441\u0438\u0438/${fileName}`,\n vacancy: v\n }\n });\n}\n\nreturn result;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1168,
1072
],
"id": "2f0f89c9-983c-4741-afb2-88412083ec42",
"name": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 Obsidian Code in JavaScript1"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst result = [];\n\nfor (const item of items) {\n result.push({\n json: item.json,\n binary: {\n data: {\n data: Buffer.from(item.json.content, 'utf8'),\n mimeType: 'text/markdown',\n fileName: item.json.fileName\n }\n }\n });\n}\n\nreturn result;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1392,
1072
],
"id": "8260f1db-6560-4802-8b4c-4a88b464207a",
"name": "\u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 Code in JavaScript"
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\nfor (let i = 0; i < items.length; i++) {\n console.log('=== Item', i, '===');\n console.log('json.fileName:', items[i].json.fileName);\n console.log('json.filePath:', items[i].json.filePath);\n console.log('binary:', items[i].binary ? 'present' : 'missing');\n if (items[i].binary) {\n console.log('binary.data:', items[i].binary.data ? 'present' : 'missing');\n console.log('binary.data.fileName:', items[i].binary.data?.fileName);\n }\n}\n\nreturn items;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1616,
1072
],
"id": "0ee7879e-eb07-49ff-9135-22cd75ce9bf6",
"name": "Code in JavaScript1"
},
{
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
80,
336
],
"id": "c992a21d-84d9-4c8a-8e35-d86bdeae16d0",
"name": "Schedule Trigger"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "27324b60-597a-4281-84f4-106a317f6672",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0431\u0435\u0437 \u043e\u043f\u044b\u0442\u0430",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "0e08c5ff-207d-459e-ab6d-f65ccfd4c61c",
"leftValue": "={{ $json.content }}",
"rightValue": "\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "641bea01-d7b5-41ac-a359-f61e0b6e2256",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0441\u0442\u0430\u0436\u0435\u0440 project",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "0697eb7b-f966-401c-94ba-8ddfb97b75e1",
"leftValue": "={{ $json.content }}",
"rightValue": "\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "5b49fdcd-00a4-4770-b455-9f0856091ea1",
"leftValue": "={{ $json.content }}",
"rightValue": "project manager",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "a8115d79-3cd1-4094-a85c-5f2dc5889705",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {
"ignoreCase": true
}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
832,
-240
],
"id": "9e0b1ce1-80e8-4705-bc01-85fb7debb526",
"name": "Filter \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b"
},
{
"parameters": {
"maxItems": 5
},
"type": "n8n-nodes-base.limit",
"typeVersion": 1,
"position": [
1184,
208
],
"id": "bfd65729-60c7-4e89-abc2-5655731bfeea",
"name": "Limit"
},
{
"parameters": {
"chatId": "673215399",
"text": "={{ $json.message }}",
"additionalFields": {
"parse_mode": "Markdown"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1744,
208
],
"id": "67a7acb7-a21b-4b3b-9540-346e824f97b3",
"name": "Send a text message",
"executeOnce": true,
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5aac1461-34f4-4dce-b2c5-72950ff0a2b6",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "641bea01-d7b5-41ac-a359-f61e0b6e2256",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0433\u0438\u0431\u0440\u0438\u0434",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "606c001a-19cb-4222-93fc-60cb3d392a58",
"leftValue": "={{ $json.content }}",
"rightValue": "\u041c\u043e\u0441\u043a\u0432\u0430",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {
"ignoreCase": true
}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
1024,
-240
],
"id": "5e67ed90-ca2b-4d98-acc0-77ee808cf276",
"name": "Filter \u0444\u043e\u0440\u043c\u0430\u0442 \u0440\u0430\u0431\u043e\u0442\u044b"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst result = [];\n\nfor (const item of items) {\n const data = item.json;\n \n // \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043e\u0442\u0432\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438\n if (data.items && Array.isArray(data.items)) {\n for (const vacancy of data.items) {\n // \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u0432 \u0442\u043e\u043c \u0436\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u0447\u0442\u043e \u0438 \u043e\u0442 \u0425\u0430\u0431\u0440\u0430\n result.push({\n json: {\n title: vacancy.name,\n description: vacancy.snippet?.requirement || vacancy.snippet?.responsibility || '',\n author: vacancy.employer?.name || '\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0430',\n link: vacancy.alternate_url,\n id: vacancy.id,\n city: vacancy.area?.name || '',\n salary: vacancy.salary ? `${vacancy.salary.from || ''} ${vacancy.salary.to || ''} ${vacancy.salary.currency || ''}`.trim() : '\u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0430',\n content: (vacancy.name + ' ' + (vacancy.snippet?.requirement || '') + ' ' + (vacancy.snippet?.responsibility || '')).toLowerCase(),\n source: 'hh.ru'\n }\n });\n }\n }\n}\n\nreturn result;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
624,
512
],
"id": "c57f9b0d-db8f-42b8-9f30-aaf049afd908",
"name": "Code in JavaScript3"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "27324b60-597a-4281-84f4-106a317f6672",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0431\u0435\u0437 \u043e\u043f\u044b\u0442\u0430",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "0e08c5ff-207d-459e-ab6d-f65ccfd4c61c",
"leftValue": "={{ $json.content }}",
"rightValue": "\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "641bea01-d7b5-41ac-a359-f61e0b6e2256",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0441\u0442\u0430\u0436\u0435\u0440 project",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "0697eb7b-f966-401c-94ba-8ddfb97b75e1",
"leftValue": "={{ $json.content }}",
"rightValue": "\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "5b49fdcd-00a4-4770-b455-9f0856091ea1",
"leftValue": "={{ $json.content }}",
"rightValue": "project manager",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "a8115d79-3cd1-4094-a85c-5f2dc5889705",
"leftValue": "={{ $json.content }}",
"rightValue": "\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {
"ignoreCase": true
}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
832,
512
],
"id": "bf86f3cd-9b0e-43e3-9b28-f3de39604f60",
"name": "Filter \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b1"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
416,
208
],
"id": "c76b0e9d-f87c-44a5-9165-dfe53c0375ea",
"name": "Merge"
},
{
"parameters": {
"url": "https://career.habr.com/vacancies/rss",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "project manager \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043c\u043b\u0430\u0434\u0448\u0438\u0439 \u0441\u0442\u0430\u0436\u0435\u0440"
}
]
},
"options": {
"batching": {
"batch": {
"batchSize": 1
}
},
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
432,
-240
],
"id": "0418e733-8765-4476-80c2-899e3411b5cd",
"name": "HTTP Request \u0425\u0430\u0431\u0440",
"notesInFlow": false
},
{
"parameters": {
"url": "https://api.hh.ru/vacancies",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "text",
"value": "project manager \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043c\u043b\u0430\u0434\u0448\u0438\u0439 \u0441\u0442\u0430\u0436\u0435\u0440"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "MyVacancyBot/1.0 (Avramenkovi07@gmail.com)"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
416,
512
],
"id": "9cec67c1-3ab2-4b15-8bd8-b5e98cd09551",
"name": "HTTP Request3 hh"
},
{
"parameters": {
"url": "https://api.hh.ru/vacancies",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "text",
"value": "project manager \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u0441\u0442\u0430\u0436\u0435\u0440 \u043c\u043b\u0430\u0434\u0448\u0438\u0439 \u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442"
},
{
"name": "area",
"value": "1"
},
{
"name": "experience",
"value": "noExperience"
},
{
"name": "employment",
"value": "full"
},
{
"name": "schedule",
"value": "remote"
},
{
"name": "per_page",
"value": "20"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
304,
1072
],
"id": "c57e6ca6-3076-4cee-a0cf-87f9695abb51",
"name": "HTTP Request"
},
{
"parameters": {
"jsCode": "// \u041a\u043e\u0434 \u0434\u043b\u044f Node \"Code\"\nconst items = $input.all();\nconst newItems = [];\n\n// \u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0434\u043b\u044f \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0447\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435, \u0442\u0435\u043c \u043b\u0443\u0447\u0448\u0435)\nconst keywords = [\n '\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', '\u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442', 'gpt', // AI\n 'no-code', 'nocode', 'low-code', // No-code\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446', '\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442', // \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f\n '\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d', 'project', 'pm', 'agile', 'scrum', '\u043a\u0430\u043d\u0431\u0430\u043d', // \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\n '\u0441\u0442\u0430\u0436\u0435\u0440', '\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a', '\u043c\u043b\u0430\u0434\u0448\u0438\u0439', 'junior', 'trainee' // \u0414\u0443\u0431\u043b\u0435\u0440\u044b \u043f\u043e\u0437\u0438\u0446\u0438\u0438\n];\n\nfor (const item of items) {\n let rating = 0;\n const content = item.json.content ? item.json.content.toLowerCase() : '';\n const title = item.json.title ? item.json.title.toLowerCase() : '';\n\n // \u0418\u0449\u0435\u043c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438\n for (const word of keywords) {\n // \u0414\u0430\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0431\u0430\u043b\u043b\u043e\u0432 \u0437\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438\n if (title.includes(word)) {\n rating += 3;\n } else if (content.includes(word)) {\n rating += 1;\n }\n }\n\n // \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0432 \u0434\u0430\u043d\u043d\u044b\u0435\n newItems.push({\n json: {\n ...item.json,\n rating: rating // \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0435 \u0441 \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u043e\u043c\n }\n });\n}\n\n// \u0421\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e \u0443\u0431\u044b\u0432\u0430\u043d\u0438\u044e \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430 (\u043e\u0442 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043a \u043c\u0435\u043d\u044c\u0448\u0438\u043c)\nnewItems.sort((a, b) => b.json.rating - a.json.rating);\n\nreturn newItems;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
736,
1072
],
"id": "85c536b2-52ca-4c95-93b9-c75a854205db",
"name": "Code in JavaScript \u0420\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u04352"
},
{
"parameters": {
"jsCode": "// \u041a\u043e\u0434 \u0434\u043b\u044f \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 hh.ru\nconst items = $input.all();\nconst newItems = [];\n\n// \u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0434\u043b\u044f \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0442\u0435 \u0436\u0435, \u0447\u0442\u043e \u0438 \u0434\u043b\u044f \u0425\u0430\u0431\u0440\u0430)\nconst keywords = [\n '\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', '\u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442', 'gpt', // AI\n 'no-code', 'nocode', 'low-code', // No-code\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446', '\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442', // \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f\n '\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d', 'project', 'pm', 'agile', 'scrum', '\u043a\u0430\u043d\u0431\u0430\u043d', // \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\n '\u0441\u0442\u0430\u0436\u0435\u0440', '\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a', '\u043c\u043b\u0430\u0434\u0448\u0438\u0439', 'junior', 'trainee', '\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442' // \u0414\u0443\u0431\u043b\u0435\u0440\u044b \u043f\u043e\u0437\u0438\u0446\u0438\u0438\n];\n\nfor (const item of items) {\n let rating = 0;\n \n // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 item.json (\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 hh.ru)\n const title = item.json.title ? item.json.title.toLowerCase() : '';\n const description = item.json.description ? item.json.description.toLowerCase() : '';\n const content = (title + ' ' + description).toLowerCase();\n \n // \u0418\u0449\u0435\u043c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438\n for (const word of keywords) {\n // \u0414\u0430\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0431\u0430\u043b\u043b\u043e\u0432 \u0437\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438\n if (title.includes(word)) {\n rating += 3;\n } \n // \u0418\u0449\u0435\u043c \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438\n if (description.includes(word)) {\n rating += 1;\n }\n }\n \n // \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0432 \u0434\u0430\u043d\u043d\u044b\u0435\n newItems.push({\n json: {\n ...item.json,\n rating: rating\n }\n });\n}\n\n// \u0421\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e \u0443\u0431\u044b\u0432\u0430\u043d\u0438\u044e \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\nnewItems.sort((a, b) => b.json.rating - a.json.rating);\n\nreturn newItems;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
512,
1072
],
"id": "fbd51649-f448-4f2a-9715-d323981506a6",
"name": "Code in JavaScript \u0420\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u04353"
},
{
"parameters": {
"url": "https://api.hh.ru/vacancies",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "text",
"value": "junior project manager"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "MyCareerBot/1.0 (Avramenkovi07@gmail.com)"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
96,
1072
],
"id": "cbf5ea9d-6582-4bf9-bd70-c671756afe45",
"name": "HH Test"
},
{
"parameters": {
"operation": "write",
"fileName": "=test.md\n",
"options": {
"append": false
}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1.1,
"position": [
976,
1072
],
"id": "fd62b6f9-0bc2-47a2-9fd9-e98d9f0b4cfd",
"name": "Read/Write Files from Disk"
},
{
"parameters": {
"operation": "createFromText",
"content": "={{$json.content}}",
"name": "={{$json.fileName}}",
"driveId": {
"__rl": true,
"value": "My Drive",
"mode": "list",
"cachedResultName": "My Drive",
"cachedResultUrl": "https://drive.google.com/drive/my-drive"
},
"folderId": {
"__rl": true,
"value": "1_k0IoJeVzf124hO5MPg-oV75Kv-Djfo4",
"mode": "id"
},
"options": {}
},
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
1616,
512
],
"id": "b30093a5-9bb9-4fe9-9abf-7ca9730ed7db",
"name": "Create file from text",
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 XML \u0432 JSON (\u0440\u0443\u0447\u043d\u043e\u0439 \u043f\u0430\u0440\u0441\u0435\u0440)\nfunction parseXMLToJSON(xmlText) {\n const result = {\n items: []\n };\n \n try {\n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0432\u0441\u0435 item'\u044b \u0438\u0437 XML\n const itemRegex = /<item>([\\s\\S]*?)<\\/item>/g;\n const items = [];\n let match;\n \n while ((match = itemRegex.exec(xmlText)) !== null) {\n items.push(match[1]);\n }\n \n // \u041f\u0430\u0440\u0441\u0438\u043c \u043a\u0430\u0436\u0434\u044b\u0439 item\n for (const itemXml of items) {\n const item = {};\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c title\n const titleMatch = itemXml.match(/<title>([\\s\\S]*?)<\\/title>/);\n if (titleMatch) item.title = titleMatch[1].replace(/<!\\[CDATA\\[|\\]\\]>/g, '').trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c description\n const descMatch = itemXml.match(/<description>([\\s\\S]*?)<\\/description>/);\n if (descMatch) item.description = descMatch[1].replace(/<!\\[CDATA\\[|\\]\\]>/g, '').trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c author\n const authorMatch = itemXml.match(/<author>([\\s\\S]*?)<\\/author>/);\n if (authorMatch) item.author = authorMatch[1].replace(/<!\\[CDATA\\[|\\]\\]>/g, '').trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c link\n const linkMatch = itemXml.match(/<link>([\\s\\S]*?)<\\/link>/);\n if (linkMatch) item.link = linkMatch[1].trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c guid (\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 ID)\n const guidMatch = itemXml.match(/<guid>([\\s\\S]*?)<\\/guid>/);\n if (guidMatch) item.id = guidMatch[1].trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c pubDate\n const dateMatch = itemXml.match(/<pubDate>([\\s\\S]*?)<\\/pubDate>/);\n if (dateMatch) item.pubDate = dateMatch[1].trim();\n \n // \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0433\u043e\u0440\u043e\u0434 \u0438\u0437 title \u0438\u043b\u0438 description\n const cityMatch = item.title?.match(/\\((.*?)\\)/) || item.description?.match(/\\((.*?)\\)/);\n if (cityMatch) item.city = cityMatch[1];\n \n // \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438\n item.content = (item.title || '') + ' ' + (item.description || '');\n \n result.items.push(item);\n }\n } catch (e) {\n console.error('\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 XML:', e);\n }\n \n return result;\n}\n\n// \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435\nconst inputData = $input.all();\n\n// \u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\nlet allVacancies = [];\n\n// \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u0445\u043e\u0434\u043d\u043e\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\nfor (const item of inputData) {\n // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c XML \u0442\u0435\u043a\u0441\u0442 \u0438\u0437 \u043f\u043e\u043b\u044f data\n const xmlText = item.json.data;\n \n if (xmlText && typeof xmlText === 'string') {\n // \u041f\u0430\u0440\u0441\u0438\u043c XML \u0432 JSON\n const parsed = parseXMLToJSON(xmlText);\n allVacancies = allVacancies.concat(parsed.items);\n }\n}\n\n// \u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u043c\u0430\u0441\u0441\u0438\u0432 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442, \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 n8n\nreturn allVacancies.map(vacancy => ({ json: vacancy }));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
624,
-240
],
"id": "f1a99ea5-2b60-4491-9797-18a35d8937bb",
"name": "\u041a\u043e\u0434 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 \u0425\u0430\u0431\u0440 Code in JavaScript"
},
{
"parameters": {
"jsCode": "return $input.all().map(item => {\n const v = item.json;\n const createdAt = new Date().toISOString().split('T')[0];\n // \u0427\u0438\u0441\u0442\u0438\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043e\u0442 \u0441\u043f\u0435\u0446\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430\n const cleanTitle = (v.title || v.role || 'vacancy').replace(/[/\\\\?%*:|\"<>\u00ab\u00bb]/g, '');\n // \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430\n item.json.fileName = `\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f ${cleanTitle} - ${createdAt}.md`;\n return item;\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
624,
208
],
"id": "e743609f-6d59-411a-9250-097e095f1ca6",
"name": "Code in JavaScript Deduplication"
},
{
"parameters": {
"jsCode": "// \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043b\u044e\u0431\u044b\u0445 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439\nconst items = $(\"Code in JavaScript Deduplication\").all();\nconst newItems = [];\n\nconst keywords = [\n '\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', '\u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442', 'gpt',\n 'no-code', 'nocode', 'low-code',\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446', '\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442',\n '\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d', 'project', 'pm', 'agile', 'scrum', '\u043a\u0430\u043d\u0431\u0430\u043d',\n '\u0441\u0442\u0430\u0436\u0435\u0440', '\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a', '\u043c\u043b\u0430\u0434\u0448\u0438\u0439', 'junior', 'trainee', '\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442'\n];\n\nfor (const item of items) {\n let rating = 0;\n \n // \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430\n let fullText = '';\n \n // \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043f\u043e\u043b\u0435 content (\u043a\u0430\u043a \u0443 \u0425\u0430\u0431\u0440\u0430)\n if (item.json.content) {\n fullText = item.json.content.toLowerCase();\n } \n // \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c title \u0438 description (\u043a\u0430\u043a \u0443 hh.ru)\n else {\n const title = item.json.title ? item.json.title.toLowerCase() : '';\n const description = item.json.description ? item.json.description.toLowerCase() : '';\n fullText = title + ' ' + description;\n }\n \n // \u0410\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043a\u0441\u0442\n for (const word of keywords) {\n if (fullText.includes(word)) {\n // \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0435\u0441\u0442\u044c \u043b\u0438 \u0441\u043b\u043e\u0432\u043e \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 (\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0431\u043e\u043d\u0443\u0441)\n const title = item.json.title ? item.json.title.toLowerCase() : '';\n if (title.includes(word)) {\n rating += 3;\n } else {\n rating += 1;\n }\n }\n }\n \n newItems.push({\n json: {\n ...item.json, // \u0412\u043e\u0442 \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u0438 id, \u0438 row_index, \u0438 \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435\n rating: rating\n }\n });\n}\n\nnewItems.sort((a, b) => b.json.rating - a.json.rating);\n\nreturn newItems;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
976,
208
],
"id": "ac370298-8eed-44a5-b47a-17aaadf6a1cd",
"name": "Code in JavaScript Ranging"
},
{
"parameters": {
"jsCode": "// \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043f\u0435\u0446\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 MarkdownV2\nfunction escapeMarkdown(text) {\n if (!text) return '';\n const specialChars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];\n let escaped = String(text);\n for (const char of specialChars) {\n escaped = escaped.replace(new RegExp('\\\\' + char, 'g'), '\\\\' + char);\n }\n return escaped;\n}\n\n// 1. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0422\u041e\u041f-5 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\nconst allItems = $(\"Code in JavaScript Ranging\").all();\n\n// 2. \u0424\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c: \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u041d\u0415\u0422 \u0432 \u0431\u0430\u0437\u0435 (\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0443\u0437\u0435\u043b IF)\n// \u041c\u044b \u0438\u0449\u0435\u043c \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438, \u0433\u0434\u0435 row_index \u043d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d (\u0437\u043d\u0430\u0447\u0438\u0442 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0438\u0445 \u043d\u0435 \u043d\u0430\u0448\u043b\u0438)\nconst newItems = allItems.filter(item => {\n try {\n // \u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432 \u0443\u0437\u043b\u0435 IF\n // \u0415\u0441\u043b\u0438 \u0432 IF \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438 row_index \u043f\u0443\u0441\u0442\u043e\u0439 \u2014 \u043e\u043d\u0430 \u043d\u043e\u0432\u0430\u044f\n return !item.json.row_index; \n } catch (e) {\n return true; // \u0415\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a, \u043b\u0443\u0447\u0448\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u044e\n }\n});\n\n// \u0415\u0441\u043b\u0438 \u041d\u041e\u0412\u042b\u0425 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 \u043d\u0435\u0442, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0433\u0440\u0443\u0441\u0442\u043d\u044b\u0439 \u0441\u043c\u0430\u0439\u043b\u0438\u043a\nconst items = $input.all();\n// \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0430\u0448\u0430 \"\u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0430\"\nif (items[0].json.isNew === false) {\n return [{\n json: {\n message: \"\ud83d\ude14 *\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e\\\\.*\\n\u041e\u0442\u0434\u044b\u0445\u0430\u0435\u043c \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u0438\u043b\u044b\\\\!\"\n }\n }];\n}\n\n// \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\nlet messageText = \"\ud83d\ude80 *\u0414\u0430\u0439\u0434\u0436\u0435\u0441\u0442 \u043d\u043e\u0432\u044b\u0445 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439 \" + escapeMarkdown(new Date().toLocaleDateString('ru-RU')) + \"*\\n\\n\";\n\nfor (let i = 0; i < newItems.length; i++) {\n const item = newItems[i].json;\n const title = escapeMarkdown(item.title || '\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f');\n const author = escapeMarkdown(item.author || '\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0430');\n let description = escapeMarkdown(item.content || item.description || '');\n \n description = description.replace(/\\n/g, ' ').substring(0, 150);\n if (description.length === 150) description += '...';\n\n messageText += `*${i + 1}\\\\. ${title}*\\n`;\n messageText += `\ud83c\udfe2 ${author}\\n`;\n messageText += `\u2b50 \u0420\u0435\u0439\u0442\u0438\u043d\u0433: ${item.rating || 0}\\n`;\n messageText += `\ud83d\udcdd ${description}\\n`;\n\n if (item.source === 'hh.ru') {\n messageText += `\ud83d\udccc \u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a: HeadHunter\\n`;\n } else {\n messageText += `\ud83d\udccc \u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a: \u0425\u0430\u0431\u0440 \u041a\u0430\u0440\u044c\u0435\u0440\u0430\\n`;\n }\n\n const link = item.link || '';\n messageText += `\ud83d\udd17 [\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438](${link})\\n`;\n\n if (i < newItems.length - 1) {\n messageText += `\\n---\\n\\n`;\n }\n}\n\nreturn [{ json: { message: messageText } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1568,
208
],
"id": "287f0d65-9c72-414d-aa21-c99d976d14b0",
"name": "Code in JavaScript2 Forming a message"
},
{
"parameters": {
"jsCode": "// 1. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0443\u0437\u043b\u0430\nconst items = $(\"Code in JavaScript Deduplication\").all();\nconst result = [];\n\n// --- \u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 ---\nfunction formatDate(date) {\n const d = new Date(date);\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n\nfunction getEmploymentType(employment) {\n const map = { 'full': 'fulltime', 'part': 'parttime', 'intern': 'intern', 'project': 'fulltime' };\n return map[(employment || '').toLowerCase()] || 'fulltime';\n}\n\nfunction getSource(source) {\n if (source === 'hh.ru') return 'hh';\n if (source === '\u0425\u0430\u0431\u0440 \u041a\u0430\u0440\u044c\u0435\u0440\u0430') return 'habr';\n return source || 'unknown';\n}\n\nfunction getMatchReason(title, content, rating) {\n const reasons = [];\n const t = (title || '').toLowerCase();\n const c = (content || '').toLowerCase();\n const keywords = {\n 'AI/\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438': ['\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', 'gpt', 'ai'],\n 'no-code/low-code': ['no-code', 'low-code'],\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f': ['\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446'],\n '\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u043c\u0438': ['project', 'pm', 'agile', 'scrum']\n };\n for (const [cat, words] of Object.entries(keywords)) {\n for (const w of words) {\n if (t.includes(w)) { reasons.push(`${cat} (+3 \u0431\u0430\u043b\u043b\u0430)`); break; }\n else if (c.includes(w)) { reasons.push(`${cat} (+1 \u0431\u0430\u043b\u043b)`); break; }\n }\n }\n return reasons.length === 0 ? `\u0420\u0435\u0439\u0442\u0438\u043d\u0433: ${rating}` : `\u0420\u0435\u0439\u0442\u0438\u043d\u0433: ${rating}\\n\\n\u2705 ${reasons.slice(0, 3).join('\\n\u2705 ')}`;\n}\n\n// 2. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0446\u0438\u043a\u043b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\nfor (const item of items) {\n const v = item.json;\n const createdAt = formatDate(new Date());\n \n // \u041e\u0447\u0438\u0441\u0442\u043a\u0430 \u0438\u043c\u0435\u043d \u0434\u043b\u044f \u0444\u0430\u0439\u043b\u043e\u0432\n const safeRole = (v.title || '\u0412\u0430\u043a\u0430\u043d\u0441\u0438\u044f').replace(/[<>:\"/\\\\|?*]/g, '').trim().substring(0, 50);\n const safeCompany = (v.author || '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f').replace(/[<>:\"/\\\\|?*]/g, '').trim().substring(0, 30);\n const dateStr = createdAt.split('-').reverse().join('-');\n const fileName = `${safeRole} - ${safeCompany} - ${dateStr}.md`;\n\n // \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u043c\u0435\u0442\u043a\u0438\n const noteContent = `---\ncompany: ${safeCompany}\nrole: ${safeRole}\nstatus: \u0432_\u0430\u043d\u0430\u043b\u0438\u0437\u0435\nsalary: ${v.salary || ''}\nlink: ${v.link || ''}\nexternal_id: ${v.id || ''}\ndeadline: \nsource: ${getSource(v.source)}\nlocation: ${v.city || ''}\nemployment_type: ${getEmploymentType(v.employment_type)}\nparsedByBot: false\nmatchScore: ${v.rating || 0}\ntags: vacancy\ncreatedAt: ${createdAt}\n---\n\n**\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439:**\n- [ ] \u0420\u0430\u0437\u0431\u043e\u0440 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u043d?\n\n**\u041f\u043e\u0447\u0435\u043c\u0443 \u0431\u043e\u0442 \u0432\u044b\u0431\u0440\u0430\u043b \u044d\u0442\u0443 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u044e**\n${getMatchReason(v.title, v.content, v.rating || 0)}\n\n**\u0421\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u043c\u0435\u0442\u043a\u0438:**\n- [[\ud83d\ude80\u00ab\u041f\u0440\u043e\u0435\u043a\u0442\u00ab\u041a\u0430\u0440\u044c\u0435\u0440\u0430\u00bbv1.0\u00bb-\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u044b\u0439 \u043f\u0443\u043d\u043a\u0442]]\n- [[\u0414\u0430\u0448\u0431\u043e\u0440\u0434-\u0412\u0430\u043a\u0430\u043d\u0441\u0438\u0438 \u043f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0443]]`;\n\n // 3. \u0412\u041e\u0417\u0412\u0420\u0410\u0429\u0410\u0415\u041c \u0420\u0415\u0417\u0423\u041b\u042c\u0422\u0410\u0422 (\u0412\u0430\u0436\u043d\u043e: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432\u0441\u0435 \u043f\u043e\u043b\u044f \u0447\u0435\u0440\u0435\u0437 v)\n result.push({\n json: {\n ...v, // \u041f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 ID \u0438 \u0432\u0441\u0435 \u043f\u043e\u043b\u044f \u0434\u0430\u043b\u044c\u0448\u0435\n ...item.json, // \u0412\u043e\u0442 \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u0438 id, \u0438 row_index, \u0438 \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435\n fileName: fileName,\n content: noteContent\n }\n });\n}\n\nreturn result;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1264,
512
],
"id": "5fc5e3df-b651-4f98-b870-e7777e8e1d29",
"name": "Code in JavaScript Obsidian Formation"
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst result = [];\n\nfor (const item of items) {\n result.push({\n json: item.json,\n binary: {\n data: {\n data: Buffer.from(item.json.content, 'utf8'),\n mimeType: 'text/markdown',\n fileName: item.json.fileName\n }\n }\n });\n}\n\nreturn result;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1440,
512
],
"id": "bff51d9f-64a1-4f99-8392-97cb8e58fbc7",
"name": "Code in JavaScript binary format"
},
{
"parameters": {
"authentication": "serviceAccount",
"documentId": {
"__rl": true,
"value": "1FMNVnZKyA7cPIBlsEQz4eRlRqYD-zwHtbHz9vBnVuh4",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "\u041b\u0438\u0441\u04421",
"mode": "name"
},
"filtersUI": {
"values": [
{
"lookupColumn": "vacancy_id",
"lookupValue": "={{ Number($json.id) }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
816,
80
],
"id": "d7f9f503-f322-4bc2-a9b5-1b63df2d6df5",
"name": "Get row(s) in sheet Examination",
"alwaysOutputData": true,
"executeOnce": true,
"credentials": {
"googleApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"authentication": "serviceAccount",
"operation": "append",
"documentId": {
"__rl": true,
"value": "1FMNVnZKyA7cPIBlsEQz4eRlRqYD-zwHtbHz9vBnVuh4",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "\u041b\u0438\u0441\u04421",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"vacancy_id": "={{ $('Code in JavaScript Deduplication').first().json.id }}"
},
"matchingColumns": [
"vacancy_id"
],
"schema": [
{
"id": "vacancy_id",
"displayName": "vacancy_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
1792,
512
],
"id": "8b9bebec-f1f9-4457-9040-3ad40614e382",
"name": "Append row in sheet",
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "46692f0f-7a77-45dd-aa74-39a674cff8d3",
"leftValue": "={{ $json.row_index }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
704,
1264
],
"id": "5c4d0bff-fca9-4dc0-9699-e94e61759d7f",
"name": "If"
},
{
"parameters": {
"jsCode": "// \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043b\u044e\u0431\u044b\u0445 \u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0439\nconst items = $(\"Code in JavaScript Deduplication\").all();\nconst newItems = [];\n\nconst keywords = [\n '\u0438\u0438', '\u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442', '\u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442', 'gpt',\n 'no-code', 'nocode', 'low-code',\n '\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446', '\u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446', '\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442',\n '\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d', 'project', 'pm', 'agile', 'scrum', '\u043a\u0430\u043d\u0431\u0430\u043d',\n '\u0441\u0442\u0430\u0436\u0435\u0440', '\u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a', '\u043c\u043b\u0430\u0434\u0448\u0438\u0439', 'junior', 'trainee', '\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442'\n];\n\nfor (const item of items) {\n let rating = 0;\n \n // \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430\n let fullText = '';\n \n // \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043f\u043e\u043b\u0435 content (\u043a\u0430\u043a \u0443 \u0425\u0430\u0431\u0440\u0430)\n if (item.json.content) {\n fullText = item.json.content.toLowerCase();\n } \n // \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c title \u0438 description (\u043a\u0430\u043a \u0443 hh.ru)\n else {\n const title = item.json.title ? item.json.title.toLowerCase() : '';\n const description = item.j
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleApigoogleDriveOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
V2.1 Career-Bot-Workflow. Uses telegram, httpRequest, readWriteFile, googleDrive. Scheduled trigger; 33 nodes.
Source: https://github.com/Gitman404/job-search-assistant/blob/18cd35fb5b2712c55c54bb1472c37eb48a6be5dc/v2-n8n/workflow/v2.1_career-bot-workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
v2.2_career-bot-workflow. Uses telegram, httpRequest, googleDrive, googleSheets. Scheduled trigger; 27 nodes.
career-bot-workflow. Uses telegram, httpRequest, googleDrive, googleSheets. Scheduled trigger; 25 nodes.
End-to-End Video Creation from user idea or transcript AI-Powered Scriptwriting using LLMs (e.g., DeepSeek via OpenRouter) Voiceover Generation with customizable TTS voices Image Scene Generation usin
Get notified when the International Space Station passes over your location - but only when you can actually see it! This workflow combines real-time ISS tracking with weather condition checks to send
This advanced n8n workflow is designed for SEO specialists, digital agency owners, webmasters, and marketing managers who need a comprehensive, automated solution to track and improve their website's