From 2fa6f3302feccc06baca272225bf96d0422d1355 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 17:48:40 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20CLAUDE.md=20=D1=81=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=D0=BD=D0=B0=20=D1=80=D1=83?= =?UTF-8?q?=D1=81=D1=81=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://claude.ai/code/session_01XJoEEfCnauwk2HXWyiJzWT --- CLAUDE.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..50b3fd0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# CLAUDE.md — Руководство по проекту + +## Обзор проекта + +Автоматический pipeline на **n8n** для сбора, обработки и публикации AI/tech новостей из Hacker News с генерацией контента через LLM. + +**Расписание:** `0 9,18 * * *` — дважды в день (09:00 и 18:00 МСК) + +## Архитектура workflow + +``` +Schedule Trigger / Manual Trigger + → Code: генерация 8 поисковых запросов (AI, LLM, GPT, agent, automation, API, startup, open source) + → HTTP Request: Hacker News Algolia API (points > 10, 50 результатов на запрос) + → Aggregate: сбор всех ответов в единый массив + → Code: дедупликация по objectID + топ-5 по points + → Basic LLM Chain (GPT-4.1-mini): генерация анонса EN + AR (150–200 символов) + → Code: парсинг JSON из LLM + сборка финального объекта + → Google Sheets: appendOrUpdate по полю title + → Telegram: отправка с inline-кнопками +``` + +## Файлы проекта + +| Файл | Назначение | +|------|-----------| +| `тестовое Прохоров Н (2).json` | Экспорт workflow из n8n (credentials заменены на заглушки `ЗАГЛУШКА`) | +| `README.md` | Документация проекта | + +## Стек технологий + +| Компонент | Решение | +|-----------|---------| +| Автоматизация | n8n (self-hosted) | +| Источник данных | Hacker News Algolia API (бесплатный, без авторизации) | +| LLM | OpenAI GPT-4.1-mini (maxTokens: 300, temperature: 0.7) | +| Хранение | Google Sheets (appendOrUpdate по title) | +| Уведомления | Telegram Bot API (chatId: 195622777) | + +## Настройка и деплой + +### 1. Импорт workflow +```bash +# Импортируй workflow.json в n8n через UI: Settings → Import from file +``` + +### 2. Credentials (заменить ЗАГЛУШКА на реальные) +- **OpenAI API** → нода `OpenAI Chat Model` +- **Google Sheets OAuth2** → нода `Append or update row in sheet` +- **Telegram API** → нода `Send a text message` + +### 3. Конфигурация нод +- **Google Sheets Document ID:** `1SffwHPqGtNOlnlvygxThUFZORdakivZWQDoaw7qI2o8` +- **Telegram Chat ID:** `195622777` +- **Активировать:** Schedule Trigger после настройки credentials + +## Ключевые ноды и логика + +### Code in JavaScript2 — генерация запросов +Создаёт 8 поисковых запросов к HN API. При добавлении новых тем — добавлять сюда. + +### Code in JavaScript1 — фильтрация +Дедупликация по `objectID`, сортировка по `points DESC`, отбор топ-5. Бросает ошибку если статей нет. + +### Basic LLM Chain — промпт +Генерирует строго JSON: `{"text_en": "...", "text_ar": "..."}`. Промпт запрещает любой другой вывод. + +### Code in JavaScript — парсинг LLM +Обходит обрыв `pairedItem` через `$('Code in JavaScript1').all()[$itemIndex]`. Нормализует все поля в строки/числа. + +## Известные ограничения + +- **LLM latency:** при 5 статьях суммарное время 30–60 сек; увеличение batch size может вызвать таймаут +- **pairedItem разрыв:** LangChain-ноды обрывают цепочку — данные берутся через `$('NodeName').all()[$itemIndex]` +- **Дедупликация между запусками:** Google Sheets сравнивает только по `title` — при изменении заголовка возможен дубль +- **Пустой source:** посты типа `Tell HN:` не имеют внешней ссылки — кнопка «Читать статью» нерабочая + +## Правила разработки + +- При изменении промпта LLM — всегда проверять что выход остаётся валидным JSON +- Новые поля в Google Sheets добавлять в schema ноды `Append or update row in sheet` +- Не трогать `batching.delayBetweenBatches: 1000` — защита от rate limit OpenAI +- Credentials никогда не коммитить; использовать заглушку `ЗАГЛУШКА` в экспортах + +## Приоритеты при доработке + +1. Добавить фильтр `created_at > now - 24h` для исключения старых постов +2. Вынести LLM-генерацию в отдельный workflow с очередью +3. Добавить Telegram-алерт при падении любого шага +4. Векторная дедупликация через embeddings + pgvector From d3fe8422a8c55fe8f0edc1e8f30cd5bb901087d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 17:52:20 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=83=D0=BD=D0=B8=D0=B2=D0=B5=D1=80=D1=81=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20CLAUDE.md=20=D0=BF=D0=BE=20=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D1=88=D0=B8=D0=BC=20=D0=BF=D1=80=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D0=BC,=20=D1=83=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B5=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://claude.ai/code/session_01XJoEEfCnauwk2HXWyiJzWT --- CLAUDE.md | 219 +++++--- README.md | 75 --- ...321\200\320\276\320\262 \320\235 (2).json" | 470 ------------------ 3 files changed, 157 insertions(+), 607 deletions(-) delete mode 100644 README.md delete mode 100644 "\321\202\320\265\321\201\321\202\320\276\320\262\320\276\320\265 \320\237\321\200\320\276\321\205\320\276\321\200\320\276\320\262 \320\235 (2).json" diff --git a/CLAUDE.md b/CLAUDE.md index 50b3fd0..4e2d976 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,90 +1,185 @@ -# CLAUDE.md — Руководство по проекту +# CLAUDE.md -## Обзор проекта +## О проекте -Автоматический pipeline на **n8n** для сбора, обработки и публикации AI/tech новостей из Hacker News с генерацией контента через LLM. +Кратко опиши проект: что он делает, для кого, какую проблему решает. -**Расписание:** `0 9,18 * * *` — дважды в день (09:00 и 18:00 МСК) +> Пример: веб-приложение для управления задачами команды. REST API на Node.js + React фронтенд. -## Архитектура workflow +--- + +## Стек технологий + +| Слой | Технология | +|------|-----------| +| Язык | TypeScript / Python / Go | +| Фреймворк | Next.js / FastAPI / Gin | +| БД | PostgreSQL + Redis | +| Тесты | Jest / Pytest / Go test | +| CI/CD | GitHub Actions | + +--- + +## Структура проекта ``` -Schedule Trigger / Manual Trigger - → Code: генерация 8 поисковых запросов (AI, LLM, GPT, agent, automation, API, startup, open source) - → HTTP Request: Hacker News Algolia API (points > 10, 50 результатов на запрос) - → Aggregate: сбор всех ответов в единый массив - → Code: дедупликация по objectID + топ-5 по points - → Basic LLM Chain (GPT-4.1-mini): генерация анонса EN + AR (150–200 символов) - → Code: парсинг JSON из LLM + сборка финального объекта - → Google Sheets: appendOrUpdate по полю title - → Telegram: отправка с inline-кнопками +src/ + api/ — маршруты и контроллеры + services/ — бизнес-логика + models/ — схемы данных + utils/ — вспомогательные функции +tests/ — тесты (зеркалируют src/) +docs/ — документация ``` -## Файлы проекта +--- -| Файл | Назначение | -|------|-----------| -| `тестовое Прохоров Н (2).json` | Экспорт workflow из n8n (credentials заменены на заглушки `ЗАГЛУШКА`) | -| `README.md` | Документация проекта | +## Окружение и установка -## Стек технологий +```bash +# Установка зависимостей +npm install + +# Переменные окружения (скопировать и заполнить) +cp .env.example .env + +# Запуск БД через Docker +docker compose up -d +``` + +Обязательные переменные окружения: +- `DATABASE_URL` — строка подключения к PostgreSQL +- `REDIS_URL` — строка подключения к Redis +- `JWT_SECRET` — секрет для подписи токенов + +--- + +## Команды разработки + +```bash +# Запуск в режиме разработки +npm run dev + +# Сборка +npm run build + +# Проверка типов +npm run typecheck + +# Линтер +npm run lint -| Компонент | Решение | -|-----------|---------| -| Автоматизация | n8n (self-hosted) | -| Источник данных | Hacker News Algolia API (бесплатный, без авторизации) | -| LLM | OpenAI GPT-4.1-mini (maxTokens: 300, temperature: 0.7) | -| Хранение | Google Sheets (appendOrUpdate по title) | -| Уведомления | Telegram Bot API (chatId: 195622777) | +# Форматирование +npm run format +``` + +--- -## Настройка и деплой +## Тестирование -### 1. Импорт workflow ```bash -# Импортируй workflow.json в n8n через UI: Settings → Import from file +# Запуск всех тестов +npm test + +# Один тест по имени +npm test -- --testNamePattern="название теста" + +# Только unit-тесты (быстро) +npm run test:unit + +# Интеграционные тесты (медленно, нужна БД) +npm run test:integration + +# С покрытием +npm run test:coverage ``` -### 2. Credentials (заменить ЗАГЛУШКА на реальные) -- **OpenAI API** → нода `OpenAI Chat Model` -- **Google Sheets OAuth2** → нода `Append or update row in sheet` -- **Telegram API** → нода `Send a text message` +**Правила:** +- Перед коммитом запускать `npm run typecheck && npm run lint` +- Не запускать полный suite при каждом изменении — используй фильтр по имени +- Новый код = новые тесты; PR без тестов не принимается + +--- + +## Стиль кода + +- ES-модули (`import/export`), не CommonJS +- `async/await` вместо `.then()/.catch()` +- `const` по умолчанию, `let` только когда нужна переменная +- Именование: `camelCase` для переменных и функций, `PascalCase` для классов и типов +- Не оставлять `console.log` в production-коде — использовать логгер +- Форматирование контролирует Prettier — не прописывай стиль вручную + +--- -### 3. Конфигурация нод -- **Google Sheets Document ID:** `1SffwHPqGtNOlnlvygxThUFZORdakivZWQDoaw7qI2o8` -- **Telegram Chat ID:** `195622777` -- **Активировать:** Schedule Trigger после настройки credentials +## Рабочие процессы -## Ключевые ноды и логика +### Новая фича +1. Создать ветку от `main`: `git checkout -b feature/название` +2. Реализовать изменения +3. Написать тесты +4. Запустить `npm run typecheck && npm run lint && npm test` +5. Создать PR с описанием что и зачем изменено + +### Исправление бага +1. Создать ветку: `git checkout -b fix/описание-бага` +2. Воспроизвести баг тестом +3. Исправить — убедиться что тест проходит +4. PR с номером issue в заголовке + +### Рефакторинг +- Не смешивать рефакторинг с новой функциональностью в одном PR +- Покрытие тестами должно оставаться на том же уровне или расти + +--- + +## Git и PR + +**Именование веток:** +``` +feature/краткое-описание +fix/что-сломано +chore/что-делается +docs/что-документируется +``` + +**Формат коммитов:** +``` +feat: добавить авторизацию через OAuth +fix: исправить утечку памяти в воркере +chore: обновить зависимости +docs: описать API эндпоинты +``` -### Code in JavaScript2 — генерация запросов -Создаёт 8 поисковых запросов к HN API. При добавлении новых тем — добавлять сюда. +**PR:** +- Заголовок: до 70 символов, описывает суть изменения +- Тело: что изменено и почему, как проверить +- Один PR — одна задача -### Code in JavaScript1 — фильтрация -Дедупликация по `objectID`, сортировка по `points DESC`, отбор топ-5. Бросает ошибку если статей нет. +--- -### Basic LLM Chain — промпт -Генерирует строго JSON: `{"text_en": "...", "text_ar": "..."}`. Промпт запрещает любой другой вывод. +## Архитектурные решения -### Code in JavaScript — парсинг LLM -Обходит обрыв `pairedItem` через `$('Code in JavaScript1').all()[$itemIndex]`. Нормализует все поля в строки/числа. +- **Сервисный слой** — вся бизнес-логика в `services/`, контроллеры только маршрутизируют +- **Репозиторий-паттерн** — доступ к БД только через репозитории, не напрямую из сервисов +- **Ошибки** — бросать типизированные ошибки (`AppError`), не возвращать `null` +- **Валидация** — только на входе системы (HTTP / очередь), не дублировать внутри -## Известные ограничения +--- -- **LLM latency:** при 5 статьях суммарное время 30–60 сек; увеличение batch size может вызвать таймаут -- **pairedItem разрыв:** LangChain-ноды обрывают цепочку — данные берутся через `$('NodeName').all()[$itemIndex]` -- **Дедупликация между запусками:** Google Sheets сравнивает только по `title` — при изменении заголовка возможен дубль -- **Пустой source:** посты типа `Tell HN:` не имеют внешней ссылки — кнопка «Читать статью» нерабочая +## Важные особенности и подводные камни -## Правила разработки +- Миграции БД запускаются автоматически при старте — не запускать вручную в production +- Тесты используют отдельную БД (`DATABASE_URL_TEST`) — не трогай основную +- Кэш Redis инвалидируется при деплое — первые запросы после деплоя будут медленнее +- Rate limiting: 100 запросов/мин на IP — учитывай в нагрузочных тестах -- При изменении промпта LLM — всегда проверять что выход остаётся валидным JSON -- Новые поля в Google Sheets добавлять в schema ноды `Append or update row in sheet` -- Не трогать `batching.delayBetweenBatches: 1000` — защита от rate limit OpenAI -- Credentials никогда не коммитить; использовать заглушку `ЗАГЛУШКА` в экспортах +--- -## Приоритеты при доработке +## Что делать нельзя -1. Добавить фильтр `created_at > now - 24h` для исключения старых постов -2. Вынести LLM-генерацию в отдельный workflow с очередью -3. Добавить Telegram-алерт при падении любого шага -4. Векторная дедупликация через embeddings + pgvector +- Коммитить `.env` файлы и секреты +- Делать прямые SQL-запросы в обход ORM без веской причины +- Игнорировать ошибки TypeScript через `// @ts-ignore` без объяснения +- Мерджить PR без прохождения CI +- Деплоить напрямую в `main` — только через PR diff --git a/README.md b/README.md deleted file mode 100644 index afe2aa5..0000000 --- a/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# HN AI News Automation — n8n Workflow - -Автоматический pipeline для сбора, обработки и публикации AI/tech новостей из Hacker News с генерацией контента через LLM. - -## Что делает workflow - -1. **Запуск по расписанию** — cron `0 9,18 * * *` (дважды в день: 9:00 и 18:00 МСК) -2. **Сбор данных** — 8 параллельных запросов к Hacker News Algolia API по ключевым темам: `AI`, `LLM`, `GPT`, `agent`, `automation`, `API`, `startup`, `open source` -3. **Фильтрация** — дедупликация по `objectID`, сортировка по `points`, отбор топ-5 статей -4. **Генерация контента** — GPT-4.1-mini создаёт анонс 150–200 символов на **английском и арабском** языках -5. **Сохранение** — запись в Google Sheets (appendOrUpdate по заголовку, без дублей) -6. **Уведомление** — отправка в Telegram с inline-кнопками для быстрых действий - -## Почему Hacker News - -Hacker News — наиболее релевантный публичный источник для AI/tech тематики: -- Algolia API бесплатный, без авторизации, стабильный -- Поле `points` позволяет фильтровать качественный контент (`points > 10`) -- Поле `num_comments` даёт сигнал о вирусности материала -- Высокая плотность постов по выбранным темам (AI, LLM, agents) — гарантирует результат при каждом запуске - -## Стек - -| Компонент | Решение | -|---|---| -| Автоматизация | n8n (self-hosted) | -| Источник данных | Hacker News Algolia API | -| LLM | OpenAI GPT-4.1-mini | -| Хранение | Google Sheets | -| Уведомления | Telegram Bot API | - -## Структура workflow - -``` -Schedule Trigger / Manual Trigger - → Code: генерация поисковых запросов (8 тем) - → HTTP Request: запрос к HN Algolia API - → Aggregate: сбор всех ответов в массив - → Code: дедупликация + топ-5 по points - → Basic LLM Chain (GPT-4.1-mini): генерация EN + AR текстов - → Code: парсинг JSON из LLM + сборка финального объекта - → Google Sheets: appendOrUpdate - → Telegram: отправка с inline-кнопками -``` - -## Узкие места - -- **LLM latency** — при 5 статьях и batch size 1 суммарное время генерации может достигать 30–60 сек. При увеличении количества статей возможны таймауты -- **Потеря pairedItem через LangChain** — LangChain-ноды обрывают цепочку `pairedItem`, из-за чего данные из предыдущих нод приходится доставать через `$('NodeName').all()[$itemIndex]` -- **Отсутствие дедупликации между запусками** — Google Sheets использует `appendOrUpdate` по полю `title`, но при изменении заголовка возможен дубль -- **Нет обработки пустого `source`** — часть HN-постов не имеет внешней ссылки (например, `Tell HN:`), кнопка «Читать статью» в таких случаях нерабочая - -## Что улучшить при масштабировании - -- **Векторная дедупликация** — сравнивать семантическую близость заголовков (embeddings + pgvector), чтобы не публиковать похожие новости -- **Очередь обработки** — вынести LLM-генерацию в отдельный workflow с очередью (Redis / n8n Queue Mode) для надёжности -- **Мультиязычность** — параметризовать языки через конфиг, чтобы легко добавлять новые направления без правки кода -- **Фильтр по свежести** — добавить фильтр `created_at > now - 24h`, чтобы исключить старые посты при повторных запусках -- **Мониторинг** — добавить ноду уведомления об ошибках (отдельный Telegram-алерт при падении любого шага) -- **A/B тест промптов** — логировать какие формулировки дают больше кликов по кнопкам для итерации промпта - -## Файлы - -- `workflow.json` — экспорт workflow из n8n (credentials заменены на заглушки) - -## Настройка - -1. Импортируй `workflow.json` в n8n -2. Подключи credentials: - - **OpenAI API** → нода `OpenAI Chat Model` - - **Google Sheets OAuth2** → нода `Append or update row in sheet` - - **Telegram API** → нода `Send a text message` -3. Укажи свой Google Sheets Document ID и Chat ID в соответствующих нодах -4. Активируй Schedule Trigger -# - diff --git "a/\321\202\320\265\321\201\321\202\320\276\320\262\320\276\320\265 \320\237\321\200\320\276\321\205\320\276\321\200\320\276\320\262 \320\235 (2).json" "b/\321\202\320\265\321\201\321\202\320\276\320\262\320\276\320\265 \320\237\321\200\320\276\321\205\320\276\321\200\320\276\320\262 \320\235 (2).json" deleted file mode 100644 index 9b81bb5..0000000 --- "a/\321\202\320\265\321\201\321\202\320\276\320\262\320\276\320\265 \320\237\321\200\320\276\321\205\320\276\321\200\320\276\320\262 \320\235 (2).json" +++ /dev/null @@ -1,470 +0,0 @@ -{ - "name": "тестовое Прохоров Н.", - "nodes": [ - { - "parameters": {}, - "type": "n8n-nodes-base.manualTrigger", - "typeVersion": 1, - "position": [ - 0, - -256 - ], - "id": "eba15466-5c44-4438-885c-f561b69f7e0f", - "name": "When clicking ‘Execute workflow’" - }, - { - "parameters": { - "rule": { - "interval": [ - { - "field": "cronExpression", - "expression": "0 9,18 * * *" - } - ] - } - }, - "type": "n8n-nodes-base.scheduleTrigger", - "typeVersion": 1.3, - "position": [ - 0, - 0 - ], - "id": "dd6d5606-a657-4cd1-912d-036597353d15", - "name": "Schedule Trigger" - }, - { - "parameters": { - "url": "=https://hn.algolia.com/api/v1/search_by_date", - "sendQuery": true, - "queryParameters": { - "parameters": [ - { - "name": "query", - "value": "={{ $json.query }}" - }, - { - "name": "tags", - "value": "story" - }, - { - "name": "hitsPerPage", - "value": "50" - }, - { - "name": "numericFilters", - "value": "points>10" - } - ] - }, - "options": { - "response": { - "response": { - "responseFormat": "json" - } - } - } - }, - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.3, - "position": [ - 624, - 0 - ], - "id": "caa6b3fe-8877-4414-90d5-6380175cede7", - "name": "HTTP Request" - }, - { - "parameters": { - "model": { - "__rl": true, - "mode": "list", - "value": "gpt-4.1-mini" - }, - "builtInTools": {}, - "options": { - "maxTokens": 300, - "temperature": 0.7 - } - }, - "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", - "typeVersion": 1.3, - "position": [ - 1328, - 192 - ], - "id": "76150338-c806-4b63-94c6-93181c5f2966", - "name": "OpenAI Chat Model", - "credentials": { - "openAiApi": { - "id": "ЗАГЛУШКА", - "name": "OpenAi account" - } - } - }, - { - "parameters": { - "promptType": "define", - "text": "=You are a social media content writer.\n\nBased on this news title: \"{{ $json.title }}\"\n\nWrite a short announcement of 150-200 characters in TWO languages.\n\nReturn ONLY this JSON format:\n{\n \"text_en\": \"your english text here\",\n \"text_ar\": \"your arabic text here\"\n}\n\nNo explanations, no markdown, only JSON.\nRespond ONLY with valid JSON, no markdown, no extra text:\n{\"text_en\": \"...\", \"text_ar\": \"...\"}", - "batching": { - "batchSize": 1, - "delayBetweenBatches": 1000 - } - }, - "type": "@n8n/n8n-nodes-langchain.chainLlm", - "typeVersion": 1.8, - "position": [ - 1328, - 0 - ], - "id": "3372303d-62e5-4092-8c7a-772ad2ba9d84", - "name": "Basic LLM Chain" - }, - { - "parameters": { - "mode": "runOnceForEachItem", - "jsCode": "const item = $input.item.json;\nconst orig = $('Code in JavaScript1').all()[$itemIndex]?.json || {};\n\nlet text = item.text || '';\n\nif (typeof text === 'object') {\n text = JSON.stringify(text);\n}\n\ntext = text.replace(/```json/g, '').replace(/```/g, '').trim();\n\nlet text_en = '';\nlet text_ar = '';\n\ntry {\n const parsed = JSON.parse(text);\n text_en = parsed.text_en || '';\n text_ar = parsed.text_ar || '';\n} catch(e) {\n const enMatch = text.match(/\"text_en\":\\s*\"([^\"]+)\"/);\n const arMatch = text.match(/\"text_ar\":\\s*\"([^\"]+)\"/);\n text_en = enMatch ? enMatch[1] : text;\n text_ar = arMatch ? arMatch[1] : '';\n}\n\nconst now = new Date();\nconst datetime = now.toLocaleString('ru-RU', {\n timeZone: 'Europe/Moscow',\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n});\n\nreturn {\n json: {\n title: String(orig.title || ''),\n source: String(orig.url || ''),\n score: Number(orig.score || 0),\n comments: Number(orig.comments || 0),\n objectID: String(orig.objectID || ''),\n text_en: String(text_en),\n text_ar: String(text_ar),\n datetime: String(datetime)\n }\n};" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1712, - 0 - ], - "id": "eebb7e53-bfd2-4d81-82bd-cedad2ca4daf", - "name": "Code in JavaScript" - }, - { - "parameters": { - "operation": "appendOrUpdate", - "documentId": { - "__rl": true, - "value": "1SffwHPqGtNOlnlvygxThUFZORdakivZWQDoaw7qI2o8", - "mode": "list", - "cachedResultName": "Тестовое Прохоров", - "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SffwHPqGtNOlnlvygxThUFZORdakivZWQDoaw7qI2o8/edit?usp=drivesdk" - }, - "sheetName": { - "__rl": true, - "value": "gid=0", - "mode": "list", - "cachedResultName": "Лист1", - "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SffwHPqGtNOlnlvygxThUFZORdakivZWQDoaw7qI2o8/edit#gid=0" - }, - "columns": { - "mappingMode": "defineBelow", - "value": { - "title": "={{ $json.title }}", - "source": "={{ $json.source }}", - "text_en": "={{ $json.text_en }}", - "text_ar": "={{ $json.text_ar }}", - "datetime": "={{ $json.datetime }}", - "score": "={{ $json.score }}", - "comments": "={{ $json.comments }}" - }, - "matchingColumns": [ - "title" - ], - "schema": [ - { - "id": "title", - "displayName": "title", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true, - "removed": false - }, - { - "id": "source", - "displayName": "source", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true - }, - { - "id": "text_en", - "displayName": "text_en", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true - }, - { - "id": "text_ar", - "displayName": "text_ar", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true - }, - { - "id": "datetime", - "displayName": "datetime", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true - }, - { - "id": "score", - "displayName": "score", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true, - "removed": false - }, - { - "id": "comments", - "displayName": "comments", - "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": [ - 1936, - 0 - ], - "id": "dba671a6-c388-4955-8e2d-1bef1a4465d7", - "name": "Append or update row in sheet", - "credentials": { - "googleSheetsOAuth2Api": { - "id": "ЗАГЛУШКА", - "name": "Google Sheets account" - } - } - }, - { - "parameters": { - "jsCode": "const data = $input.first().json.data;\n\nconst allHits = data.flatMap(item => item.hits || []);\n\nconst seen = new Set();\nconst unique = allHits.filter(hit => {\n if (seen.has(hit.objectID)) return false;\n seen.add(hit.objectID);\n return true;\n});\n\nif (unique.length === 0) {\n throw new Error('No articles found');\n}\n\nconst top5 = unique\n .sort((a, b) => (b.points || 0) - (a.points || 0))\n .slice(0, 5);\n\nreturn top5.map(hit => ({\n json: {\n title: hit.title,\n url: hit.url,\n score: hit.points || 0,\n comments: hit.num_comments || 0,\n objectID: hit.objectID, \n created_at: hit.created_at\n }\n}));" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1072, - 0 - ], - "id": "71b61366-cb4e-4684-beca-c7e6de4f70c7", - "name": "Code in JavaScript1" - }, - { - "parameters": { - "jsCode": "return [\n { json: { query: 'AI' } }, // 27% всех постов\n { json: { query: 'LLM' } }, // 45% рост за год\n { json: { query: 'startup' } }, // 9% всех постов\n { json: { query: 'automation' } }, // low-code/no-code +22%\n { json: { query: 'API' } }, // вечно популярно\n { json: { query: 'open source' } }, // всегда много статей\n { json: { query: 'agent' } }, // AI agents тренд 2026\n { json: { query: 'GPT' } } // стабильно много\n];" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 416, - 0 - ], - "id": "bef2bc64-84ef-40b6-bd5a-7f7c72c4ff70", - "name": "Code in JavaScript2" - }, - { - "parameters": { - "aggregate": "aggregateAllItemData", - "options": {} - }, - "type": "n8n-nodes-base.aggregate", - "typeVersion": 1, - "position": [ - 832, - 0 - ], - "id": "29d92148-6f71-46f3-b2e3-1dd500ca5c7d", - "name": "Aggregate" - }, - { - "parameters": { - "chatId": "195622777", - "text": "=📰 {{ $('Code in JavaScript').item.json.title }}\n\n🌐 EN: {{ $('Code in JavaScript').item.json.text_en }}\n🇸🇦 AR: {{ $('Code in JavaScript').item.json.text_ar }}\n\n⭐ Score: {{ $json.score }} | 💬 Comments: {{ $json.comments }}\n🕐 {{ $json.datetime }}", - "replyMarkup": "inlineKeyboard", - "inlineKeyboard": { - "rows": [ - { - "row": { - "buttons": [ - { - "text": "📖 Читать статью", - "additionalFields": { - "callback_data": "cads" - } - }, - { - "text": "💬 Создать Оффер", - "additionalFields": { - "callback_data": "daw" - } - } - ] - } - } - ] - }, - "additionalFields": { - "appendAttribution": false, - "disable_notification": true, - "parse_mode": "HTML" - } - }, - "type": "n8n-nodes-base.telegram", - "typeVersion": 1.2, - "position": [ - 2240, - 0 - ], - "id": "b7dd2e9c-ae0f-4e55-bfec-a1fe337255ab", - "name": "Send a text message", - "webhookId": "757dcab1-1f93-489e-925b-4a4619e3fe8d", - "credentials": { - "telegramApi": { - "id": "ЗАГЛУШКА", - "name": "ЗАГЛУШКА" - } - } - } - ], - "pinData": {}, - "connections": { - "Schedule Trigger": { - "main": [ - [ - { - "node": "Code in JavaScript2", - "type": "main", - "index": 0 - } - ] - ] - }, - "When clicking ‘Execute workflow’": { - "main": [ - [ - { - "node": "Code in JavaScript2", - "type": "main", - "index": 0 - } - ] - ] - }, - "HTTP Request": { - "main": [ - [ - { - "node": "Aggregate", - "type": "main", - "index": 0 - } - ] - ] - }, - "OpenAI Chat Model": { - "ai_languageModel": [ - [ - { - "node": "Basic LLM Chain", - "type": "ai_languageModel", - "index": 0 - } - ] - ] - }, - "Basic LLM Chain": { - "main": [ - [ - { - "node": "Code in JavaScript", - "type": "main", - "index": 0 - } - ] - ] - }, - "Code in JavaScript": { - "main": [ - [ - { - "node": "Append or update row in sheet", - "type": "main", - "index": 0 - } - ] - ] - }, - "Code in JavaScript1": { - "main": [ - [ - { - "node": "Basic LLM Chain", - "type": "main", - "index": 0 - } - ] - ] - }, - "Code in JavaScript2": { - "main": [ - [ - { - "node": "HTTP Request", - "type": "main", - "index": 0 - } - ] - ] - }, - "Aggregate": { - "main": [ - [ - { - "node": "Code in JavaScript1", - "type": "main", - "index": 0 - } - ] - ] - }, - "Append or update row in sheet": { - "main": [ - [ - { - "node": "Send a text message", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "active": false, - "settings": { - "executionOrder": "v1", - "availableInMCP": false - }, - "versionId": "6bc23e5f-a975-4e82-ba7d-3d953cb2e3d5", - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "aef6f84705ac4a62692720e5c63278baa6530dfeabfb104bdd27dc8f1fb6d88f" - }, - "id": "AkIMEcYw5CHKusMx", - "tags": [] -} \ No newline at end of file