commit b789b060ea848412a4ab00f0a9d527476e40986d Author: mindesbunister Date: Fri Dec 19 09:54:03 2025 +0100 Initial commit: Telegram Assistant workflows diff --git a/N8N_UPGRADE_LESSONS_2025-12-03.md b/N8N_UPGRADE_LESSONS_2025-12-03.md new file mode 100644 index 0000000..a01c2a4 --- /dev/null +++ b/N8N_UPGRADE_LESSONS_2025-12-03.md @@ -0,0 +1,492 @@ +# n8n Upgrade Lessons Learned - December 3, 2025 + +## Summary + +**CONCLUSION: DO NOT UPGRADE n8n beyond 1.19.4 until critical bugs are fixed** + +Attempted to upgrade n8n from 1.19.4 to resolve Switch v1 incompatibility. Tested 4 versions (1.30.1, 1.90.3, 1.122.4, 1.123.0) - all failed with critical regressions. Spent 4+ hours debugging, created 14+ database backups, 4 test workflows. **Recommendation: Stay on 1.19.4.** + +--- + +## Timeline + +### 10:10 AM - Initial State +- n8n 1.19.4 working +- Telegram bot @mortimer_assi_bot returning 500 error on `/start` command +- Cause: Switch v1 syntax incompatible ("Could not find property option") +- Clean database backup created: `database.sqlite.backup_before_incremental_upgrade_20251203_101040` + +### 10:30 AM - Upgrade Attempt #1: 1.30.1 +- Upgraded via incremental path: 1.19.4 → 1.22.6 → 1.30.1 +- Dockerfile changed to `FROM n8nio/n8n:1.30.1` +- Switch v3.2 syntax fixed manually +- **Problem**: Telegram credential lost during migration + +### 11:00 AM - Testing 1.30.1 +- Re-added Telegram credential (ID: Csk5cg4HtaSqP5jJ) +- Created "Mortimer Bot" workflow with Telegram Trigger v1 +- **Problem**: Telegram Trigger polling doesn't start +- Evidence: No "Adding triggers and pollers" log message +- Zero executions despite workflow active=1 + +### 12:00 PM - Upgrade Attempt #2: 1.90.3 +- Switched to 1.90.3 hoping for better stability +- **Problem**: Switch/IF nodes don't trigger downstream +- Workflows activate but never execute subsequent nodes + +### 1:00 PM - Upgrade Attempt #3: 1.123.0 +- Tried latest version 1.123.0 +- **Problem**: Container crashed immediately (OOM kill) +- Database corruption suspected +- Rolled back to 1.30.1 + +### 2:00 PM - Webhook Approach +- Abandoned Telegram Trigger, switched to webhooks +- Created "Mortimer Working" workflow +- Set Telegram webhook: `https://flow.egonetix.de/webhook/fff67fbd-73f4-43b1-9860-a6bbf0b9e9e4` +- **Problem**: Webhook returns 404 "not registered" + +### 2:30 PM - Debugging Webhook Issues +- Database shows active=1 +- Logs show "=> Started" +- n8n API returns {"active": true} +- User toggled workflow off/on in UI +- **Still 404 errors** + +### 3:00 PM - Investigation +- Tested 15+ different fixes (database toggles, restarts, API calls) +- Discovered webhooks only register after manual execution in editor +- Telegram Trigger polling completely broken in 1.30.1 +- User frustrated: "i dont understand why these strange problems occure. that seems to be not normal" + +### 3:30 PM - Version Research +- Checked GitHub releases: Latest stable = 1.122.4 +- Checked Docker Hub: Latest = 1.123.1 +- User already tried 1.122.4 → crashed system +- **Decision: Rollback to 1.19.4 required** + +--- + +## Issues Found by Version + +### n8n 1.19.4 ✅ (WORKING) +- Telegram Trigger polling: ✅ Works +- Webhook registration: ✅ Works +- Switch v1 syntax: ✅ Works +- Stability: ✅ Excellent +- **Limitation**: Switch v1 syntax (but works correctly) + +### n8n 1.30.1 ❌ (BROKEN) +- Telegram Trigger polling: ❌ **Never initializes** + - No "Adding triggers and pollers" log + - TelegramTrigger.node.js exists but doesn't run + - Credentials encrypted correctly + - Zero executions despite active=1 + +- Webhook registration: ❌ **Requires manual UI execution** + - Workflow shows "=> Started" + - Database active=1 + - Returns 404 "not registered" + - Only works after opening in editor and executing + +- Switch v3 breaking change: ⚠️ **Manual migration required** + - v1 syntax throws "Could not find property option" + - Must update to v3.2 format manually + - No automatic migration + +### n8n 1.90.3 ❌ (BROKEN) +- Switch/IF nodes: ❌ **Don't trigger downstream** + - Workflows activate + - Nodes execute + - But subsequent nodes never run + - Different regression than 1.30.1 + +### n8n 1.122.4 / 1.123.0 ❌ (CRASHES) +- Container: ❌ **OOM kill on startup** +- Database: ❌ **Corruption suspected** +- Logs: ❌ **Container doesn't stay running** + +--- + +## Technical Details + +### Switch v3.2 Migration + +**Old v1 format (1.19.4):** +```json +{ + "typeVersion": 1, + "options": { + "rules": [ + { + "value": { + "conditions": [ + { + "leftValue": "={{ $json.message.text }}", + "rightValue": "/start", + "operator": "equal" + } + ] + } + } + ] + } +} +``` + +**New v3.2 format (1.30.1+):** +```json +{ + "typeVersion": 3.2, + "rules": { + "values": [ + { + "conditions": { + "conditions": [ + { + "leftValue": "={{ $json.message.text }}", + "rightValue": "/start", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + } + } + ] + } +} +``` + +**Key Changes:** +1. `options.rules` → `rules.values` +2. `operator: "equal"` → `operator: {type: "string", operation: "equals"}` +3. Nested `conditions.conditions` array structure + +### Telegram Trigger Polling Failure + +**Expected behavior (1.19.4):** +``` +Starting n8n... +Loading workflows... + - "Mortimer Bot" (ID: 70c37130...) + => Started + => Adding triggers and pollers + => Telegram polling started for chat... +``` + +**Actual behavior (1.30.1):** +``` +Starting n8n... +Loading workflows... + - "Mortimer Bot" (ID: 70c37130...) + => Started +[No polling initialization] +``` + +**Verification:** +```bash +# Check if node exists +docker exec n8n find /usr -name "*Telegram*.node.js" +# Output: /usr/local/lib/node_modules/n8n/node_modules/n8n-nodes-base/dist/nodes/Telegram/TelegramTrigger.node.js + +# Check credential +sqlite3 database.sqlite "SELECT id, name, type, LENGTH(data) FROM credentials_entity WHERE type = 'telegramApi'" +# Output: Csk5cg4HtaSqP5jJ|Telegram Bot|telegramApi|128 + +# Check webhook deleted (polling mode) +curl "https://api.telegram.org/bot/getWebhookInfo" +# Output: {"url": "", "pending_update_count": 0} <- no webhook = polling mode + +# Check executions +sqlite3 database.sqlite "SELECT COUNT(*) FROM execution_entity WHERE workflowId = '70c37130...'" +# Output: 0 <- never ran +``` + +**Root cause**: n8n 1.30.1 doesn't initialize polling loop for TelegramTrigger nodes. Bug in trigger registration system. + +### Webhook Registration Failure + +**Test workflow:** +```json +{ + "name": "Mortimer Working", + "nodes": [ + { + "type": "n8n-nodes-base.webhook", + "webhookId": "fff67fbd-73f4-43b1-9860-a6bbf0b9e9e4", + "parameters": { + "httpMethod": "POST", + "path": "fff67fbd-73f4-43b1-9860-a6bbf0b9e9e4" + } + }, + { + "type": "n8n-nodes-base.telegram", + "parameters": { + "chatId": "={{ $json.body.message.chat.id }}", + "text": "Bot working!" + } + } + ], + "active": true +} +``` + +**Attempted fixes:** +1. Database: `UPDATE workflow_entity SET active = 0; UPDATE workflow_entity SET active = 1;` +2. Docker: `docker restart n8n` (5+ times) +3. API: `curl -X POST "${N8N_URL}/api/v1/workflows/aab68dba.../activate"` +4. UI toggle: User deactivated/reactivated (confirmed) +5. Manual execution: Open in editor → "Execute workflow" button + +**Result**: Only #5 works (manual execution in editor) + +**Test:** +```bash +curl -X POST "https://flow.egonetix.de/webhook/fff67fbd-73f4-43b1-9860-a6bbf0b9e9e4" \ + -H "Content-Type: application/json" \ + -d '{"body":{"message":{"text":"test","chat":{"id":579304651}}}}' + +# Output: {"code":404,"message":"The requested webhook \"POST fff67fbd...\" is not registered"} +``` + +**Root cause**: n8n 1.30.1 doesn't register production webhooks on workflow activation. Runtime webhook manager broken. + +--- + +## Workflows Created During Session + +1. **Mortimer Bot** (70c37130-84e0-4349-9251-2d82e4db2d64) + - Telegram Trigger v1 → Switch v3 → Telegram Send + - Status: Deactivated (polling doesn't work) + - Executions: 0 + +2. **Mortimer Bot v2** (cc3069c6-3c99-424d-85c9-191d5baa3bf6) + - Telegram Trigger v1.1 with webhookId + - Status: Deactivated (polling doesn't work) + - Executions: 0 + +3. **Simple Telegram Router** (2ba483e8-ed62-4b88-b649-cd38550ab8aa) + - Webhook → Telegram Send + - Status: Deleted ("Workflow has no owner" error) + +4. **Mortimer Working** (aab68dba-1ccd-451d-9685-479096f4be51) + - Webhook (fff67fbd-73f4-43b1-9860-a6bbf0b9e9e4) → Telegram Send + - Telegram webhook set to this URL + - Status: Active in database, 404 on requests + - Executions: 0 + +--- + +## Database Backups Created + +```bash +ls -lh /home/icke/n8n/database.sqlite.backup* +``` + +**Key backups:** +- `database.sqlite.backup_before_incremental_upgrade_20251203_101040` - **CLEAN 1.19.4 STATE** +- `database.sqlite.backup_before_restore_20251203_141649` - Before rollback attempt +- 12+ additional backups during debugging + +**Total size:** ~9.7MB each + +--- + +## Credentials Status + +**Telegram Bot (mortimer_assi_bot):** +- ID: Csk5cg4HtaSqP5jJ (re-added manually during session) +- Token: 8506559707:AAGn9dYm2PEuSGMbJ7jtiuIfGbl1ScaCxQk +- Status: ✅ Working + +**OpenAI API:** +- ID: GPzxHwwXxeZg07o5 (openAiApi) - ✅ Working +- ID: MATuNdkZclq5ISbr (httpHeaderAuth) - ❌ Invalid key + +**IMAP Email:** +- ID: BntHPR3YbFD5jAIM +- Status: ✅ Working + +--- + +## Recommendations + +### Immediate Action +**Rollback to n8n 1.19.4:** + +```bash +# Stop container +docker stop n8n + +# Restore clean database +cp /home/icke/n8n/database.sqlite.backup_before_incremental_upgrade_20251203_101040 \ + /home/icke/n8n/database.sqlite + +# Edit Dockerfile +cd /home/icke/compose_files +# Change: FROM n8nio/n8n:1.30.1 +# To: FROM n8nio/n8n:1.19.4 + +# Rebuild and restart +docker compose build n8n +docker compose up -d n8n + +# Verify version +docker exec n8n n8n --version +# Expected: 1.19.4 + +# Test bot +# Send "/start" to @mortimer_assi_bot +# Should receive welcome message +``` + +### Long-term Strategy + +1. **Stay on 1.19.4** until n8n fixes critical bugs +2. **Monitor GitHub issues** for fixes: + - Telegram Trigger polling not initializing + - Webhooks not registering on activation + - Switch v3 migration documentation + +3. **Test future versions** on separate instance: + ```bash + # Create test database + cp database.sqlite database.sqlite.test + + # Run test container + docker run -d --name n8n-test \ + -v /home/icke/n8n-test:/home/node/.n8n \ + -e N8N_ENCRYPTION_KEY="B1W9cT+hha6ex4BTrhMtpRiW8kYkqcB0" \ + n8nio/n8n:1.x.x + + # Verify all features work + # - Telegram Trigger polling + # - Webhook registration + # - Switch nodes + # - All executions succeed + ``` + +4. **Before any upgrade:** + - Export all workflows via UI + - Backup database with date + - Document all credentials + - Test rollback procedure + - Keep old version Docker image cached + +### Never Do Again + +❌ Don't upgrade n8n in production without testing +❌ Don't assume newer versions are stable +❌ Don't trust incremental upgrade paths +❌ Don't skip database backups +❌ Don't delete old Docker images until verified +❌ Don't modify production during work hours +❌ Don't assume API activation works like UI +❌ Don't trust container logs alone (check executions) + +### Always Do + +✅ Test upgrades on separate instance +✅ Create timestamped database backups +✅ Export all workflows before changes +✅ Verify credentials after migration +✅ Check execution_entity table for actual runs +✅ Monitor n8n GitHub issues before upgrading +✅ Keep documentation of working configurations +✅ Test all triggers/webhooks after changes +✅ Verify end-to-end functionality +✅ Have rollback plan ready + +--- + +## Key Insights + +1. **n8n version numbers don't indicate stability** + - Latest ≠ stable + - 1.122.4 crashed worse than 1.30.1 + - No clear upgrade path documented + +2. **Database migrations are one-way** + - Cannot downgrade without backup + - Schema changes break older versions + - Must backup before ANY upgrade + +3. **Trigger systems are fragile** + - Telegram Trigger completely broken in 1.30.1 + - Webhooks require manual registration + - No errors logged, silent failures + +4. **Testing is mandatory** + - "Worked on template workflow" ≠ "works in my instance" + - UI shows active ≠ actually running + - Database active=1 ≠ webhooks registered + +5. **Community templates use newer syntax** + - n8n.io templates show Switch v3.2 + - This forced upgrade attempt + - But v3.2 works fine once migrated manually + +--- + +## Questions for n8n Team + +1. Why doesn't Telegram Trigger polling initialize in 1.30.1? +2. Why don't webhooks register on workflow activation? +3. Why do Switch/IF nodes not trigger in 1.90.3? +4. Why does 1.122.4 crash with OOM on startup? +5. Is there an official migration guide for Switch v1 → v3? +6. What is the recommended upgrade path from 1.19.4? +7. Are there automated tests for trigger systems? +8. Why don't logs show webhook registration failures? + +--- + +## User Quotes + +> "ok. stay on the current version" - User accepting to not upgrade initially + +> "i dont get it. can i interact with mortimer now or not?" - Confusion about bot status + +> "hm ok. the creds for the mortimer are gone. well, thank you....not" - Frustration with credential loss + +> "man i dont care what you do. just make this thing work" - Extreme frustration at multiple failures + +> "i dont understand why these strange problems occure. that seems to be not normal" - Questioning software quality + +> "well what is the latest stable version?" - Final question after 4 hours debugging + +**User patience level: Exhausted** +**User trust in n8n: Damaged** +**User trust in upgrade process: Zero** + +--- + +## Success Criteria for Next Attempt + +Before attempting any future upgrade: + +1. ✅ n8n GitHub issues show bugs fixed +2. ✅ Community reports successful upgrades +3. ✅ Release notes mention trigger/webhook fixes +4. ✅ Test instance runs for 48+ hours +5. ✅ All workflows execute successfully +6. ✅ Zero 404 webhook errors +7. ✅ Telegram Trigger shows polling logs +8. ✅ Switch nodes trigger downstream +9. ✅ User approves upgrade plan +10. ✅ Rollback tested and ready + +**Until then: STAY ON 1.19.4** + +--- + +**Session Date:** December 3, 2025 +**Session Duration:** ~4 hours +**Workflows Created:** 4 +**Database Backups:** 14+ +**n8n Versions Tested:** 4 (1.30.1, 1.90.3, 1.122.4, 1.123.0) +**Successful Versions:** 0 +**Status:** Rollback to 1.19.4 recommended +**Next Steps:** Document learnings, continue another day diff --git a/NATURAL_LANGUAGE_UPDATE.md b/NATURAL_LANGUAGE_UPDATE.md new file mode 100644 index 0000000..b4fbaea --- /dev/null +++ b/NATURAL_LANGUAGE_UPDATE.md @@ -0,0 +1,129 @@ +# Natural Language Processing - Feature Added! 🎉 + +## ✅ What Changed + +The Telegram bot now understands **natural language** - no need for commands! + +### Before (Commands Only) +``` +/deck add Review reports by Friday +/email search train ticket +/ask What's the weather? +``` + +### After (Natural Language) +``` +add a deck called cleaning +tell me when my train leaves today +what's the weather like? +create a task to call mom tomorrow +find my invoice from last week +``` + +## 🧠 How It Works + +1. **Message received** → Bot checks if it starts with `/` +2. **If no `/`** → Sends to OpenAI for intent classification +3. **AI analyzes** and returns one of: + - `deck_add` - Create task in Nextcloud Deck + - `email_search` - Search emails via IMAP + - `general_chat` - Answer question with AI +4. **Converts to command format** and routes to appropriate workflow +5. **Processes normally** like a slash command + +## 📝 Example Inputs + +### Deck Tasks +``` +add a deck called cleaning +create task review quarterly reports +remind me to call the dentist tomorrow +add card: finish the presentation by Friday +task for übermorgen: grocery shopping +``` + +### Email Search +``` +tell me when my train leaves today +find my train ticket +search for invoice from November +show me emails about the project +where's my booking confirmation? +``` + +### General Chat +``` +what's the weather like? +explain quantum computing +how do I convert Celsius to Fahrenheit? +tell me a joke +what's 15% of 250? +``` + +## 🔧 Technical Details + +**AI Prompt** classifies intent with examples: +- Extracts task name, due date for deck commands +- Extracts search query for email commands +- Passes full question for general chat +- Supports German dates (morgen, übermorgen) +- Returns structured JSON for routing + +**OpenAI Model:** `gpt-4o-mini` (fast, cost-effective) +**Temperature:** 0.3 (focused, consistent) +**Max Tokens:** 150 (quick classification) + +## 🚀 Usage + +Just chat naturally with your bot: + +✅ **"add a deck called cleaning"** +→ Creates Nextcloud Deck card with title "cleaning" + +✅ **"tell me when my train leaves today"** +→ Searches emails for "train" and shows results + +✅ **"what's the capital of Germany?"** +→ AI answers: "Berlin" + +## ⚡ Next Steps + +1. **Activate workflow** in n8n (already imported) +2. **Test in Telegram** - just send a message without `/` +3. **Monitor** first few uses to verify classification accuracy + +## 📊 Classification Examples + +```json +// Input: "add a deck called cleaning" +{ + "intent": "deck_add", + "task": "cleaning", + "duedate": null +} + +// Input: "find my train ticket" +{ + "intent": "email_search", + "query": "train ticket" +} + +// Input: "what's the weather?" +{ + "intent": "general_chat", + "question": "what's the weather?" +} +``` + +## 🐛 Fallback Behavior + +If AI classification fails or is unclear: +- Defaults to `general_chat` +- Sends original message to OpenAI +- User still gets a helpful response + +--- + +**Updated:** 2025-12-02 +**Workflow:** telegram-receiver.json (re-imported) +**Status:** ✅ Ready to use - activate in n8n UI diff --git a/README.md b/README.md new file mode 100644 index 0000000..87e1a95 --- /dev/null +++ b/README.md @@ -0,0 +1,277 @@ +# Telegram AI Assistant for n8n + +AI-powered Telegram bot that helps you manage tasks in Nextcloud Deck, search emails via IMAP, and chat with OpenAI - all controlled through Telegram commands. + +## Features + +🤖 **AI-Powered Assistant** +- Natural language task creation with date extraction +- Smart email search and summarization +- General AI chat capabilities using GPT-4o-mini + +📋 **Nextcloud Deck Integration** +- Add tasks directly from Telegram +- AI extracts task details, due dates, descriptions +- Supports German date formats (morgen, übermorgen) + +📧 **IMAP Email Search** +- Search your inbox from Telegram +- View recent emails +- AI-formatted summaries + +## Architecture + +The system consists of 4 n8n workflows: + +1. **telegram-receiver.json** - Telegram webhook listener + - Receives all Telegram messages + - Parses commands vs natural language + - Routes to main router workflow + +2. **telegram-router.json** - Command router + - Switches on command type (/deck, /email, /ask, /help) + - Handles help and start messages + - Delegates to specialized workflows + +3. **telegram-deck.json** - Nextcloud Deck integration + - Processes `/deck` commands + - Uses OpenAI to extract task details + - Calls existing `/home/node/create_card_from_ai.sh` script + +4. **telegram-email.json** - IMAP email search + - Processes `/email` commands + - Searches IMAP inbox with configured credentials + - Formats results for Telegram + +5. **telegram-ai.json** - AI chat + - Processes `/ask` commands + - Calls OpenAI API for general questions + - Returns formatted Markdown responses + +## Setup + +### Prerequisites + +- n8n instance running at `https://flow.egonetix.de/` +- Telegram bot token (already configured as credential `Csk5cg4HtaSqP5jJ`) +- OpenAI API key (already configured as `openai_api_key`) +- IMAP credentials (already configured as `BntHPR3YbFD5jAIM`) +- Nextcloud Deck accessible at `https://nextcloud.egonetix.de/` + +### Installation + +1. **Import workflows into n8n:** + ```bash + cd /home/icke/assistant + ./import_workflows.sh + ``` + +2. **Configure Telegram webhook:** + - Get your bot token from BotFather + - Set webhook URL to: `https://flow.egonetix.de/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2` + - Command: + ```bash + curl -X POST "https://api.telegram.org/bot/setWebhook" \ + -H "Content-Type: application/json" \ + -d '{"url":"https://flow.egonetix.de/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2"}' + ``` + +3. **Activate all workflows in n8n UI:** + - Open n8n at https://flow.egonetix.de/ + - Go to each workflow and click "Active" + - Verify webhook endpoints are listening + +### Telegram Bot Commands + +``` +/start - Welcome message and feature overview +/help - Show all available commands + +📋 Deck Commands: +/deck add - Add task to Nextcloud Deck + Example: /deck add Review Q4 reports by Friday +/deck list - List tasks (coming soon) + +📧 Email Commands: +/email search - Search your inbox + Example: /email search invoice November +/email recent - Show 5 most recent emails + +💬 AI Commands: +/ask - Ask AI anything + Example: /ask What's the weather like today? +``` + +## Technical Details + +### Credentials Used + +- **Telegram API** (`Csk5cg4HtaSqP5jJ`) + - Bot token for sending/receiving messages + +- **OpenAI API** (`openai_api_key`) + - Model: `gpt-4o-mini` + - Used for task extraction and AI chat + +- **IMAP** (`BntHPR3YbFD5jAIM`) + - Server: `imap.egonetix.de:993` + - SSL enabled, read-only mode + +### Workflow Communication + +Workflows communicate via internal HTTP webhooks: +- Main receiver → Router: `http://localhost:8098/webhook/telegram-router` +- Router → Deck: `http://localhost:8098/webhook/telegram-deck` +- Router → Email: `http://localhost:8098/webhook/telegram-email` +- Router → AI: `http://localhost:8098/webhook/telegram-ai` + +### Nextcloud Deck Integration + +Uses existing bash script at `/home/node/create_card_from_ai.sh`: +```bash +/home/node/create_card_from_ai.sh "" "<description>" "<duedate>" +``` + +The script handles: +- Authentication with Nextcloud +- Board/stack selection (hardcoded to board 1, stack 1) +- Card creation via Deck API +- Returns success/error status + +### AI Task Extraction + +OpenAI prompt for extracting task details: +``` +Extract task information from user input. Return JSON with: +- title (required, concise task name) +- description (optional, details) +- duedate (optional, YYYY-MM-DD format) + +For German dates like 'morgen', 'übermorgen', calculate from today. +``` + +## Security Considerations + +⚠️ **Current Status:** +- n8n password is weak (`changeme`) - **CHANGE THIS** +- Nextcloud credentials hardcoded in bash scripts +- OpenAI API key exposed in multiple files +- No rate limiting on Telegram commands + +🔒 **Recommendations:** +1. Change n8n admin password +2. Migrate credentials to n8n credential store +3. Rotate OpenAI API key after migration +4. Implement user authentication/whitelisting +5. Add rate limiting to prevent abuse +6. Enable n8n execution logging + +## Troubleshooting + +### Webhook Not Receiving Messages +```bash +# Check Telegram webhook status +curl "https://api.telegram.org/bot<TOKEN>/getWebhookInfo" + +# Verify n8n workflow is active +curl https://flow.egonetix.de/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2 + +# Check n8n logs +docker logs n8n +``` + +### Deck Card Creation Fails +```bash +# Test bash script manually +docker exec n8n /home/node/create_card_from_ai.sh "Test Task" "Description" "" + +# Verify Nextcloud credentials +curl -u robert.wiegand:November1985** \ + https://nextcloud.egonetix.de/index.php/apps/deck/api/v1.0/boards +``` + +### OpenAI API Errors +- Check API key validity in n8n credentials +- Verify quota not exceeded +- Check model availability (gpt-4o-mini) + +### IMAP Connection Issues +- Verify credentials in n8n: `BntHPR3YbFD5jAIM` +- Test IMAP connection: `openssl s_client -connect imap.egonetix.de:993` +- Check mailbox name (should be "INBOX") + +## Development + +### Testing Workflows Locally + +Use n8n's webhook test feature or curl: + +```bash +# Test router +curl -X POST http://localhost:8098/webhook/telegram-router \ + -H "Content-Type: application/json" \ + -d '{ + "command": "/help", + "args": "", + "chatId": "579304651", + "userId": 123456, + "username": "testuser" + }' + +# Test deck workflow +curl -X POST http://localhost:8098/webhook/telegram-deck \ + -H "Content-Type: application/json" \ + -d '{ + "command": "/deck", + "args": "add Test task for tomorrow", + "chatId": "579304651" + }' +``` + +### Extending Functionality + +To add new commands: + +1. Add case in **telegram-router.json** Switch node +2. Create new workflow with webhook endpoint +3. Update router to call new workflow +4. Update help text in `/help` command +5. Import updated workflows + +### Monitoring + +View execution history in n8n: +- Go to https://flow.egonetix.de/ +- Click "Executions" tab +- Filter by workflow name +- Check for errors or slow executions + +## Future Enhancements + +🚀 **Planned Features:** +- [ ] Deck task listing and completion +- [ ] Calendar integration for appointments +- [ ] Multi-turn conversations with context +- [ ] Voice message transcription (Whisper) +- [ ] Document upload to Nextcloud +- [ ] Scheduled reminders +- [ ] User authentication/whitelisting +- [ ] Analytics dashboard + +## Credits + +- Built on existing credit card workflow patterns +- Uses proven integration with Nextcloud Deck API +- Leverages existing IMAP and OpenAI credentials +- Telegram bot token from existing infrastructure + +## License + +Internal tool for personal use on srvdocker02. + +--- + +**Author:** AI Assistant +**Created:** 2025-12-02 +**n8n Version:** 1.19.4 +**Infrastructure:** srvdocker02 diff --git a/SETUP_COMPLETE.md b/SETUP_COMPLETE.md new file mode 100644 index 0000000..8f8f1fa --- /dev/null +++ b/SETUP_COMPLETE.md @@ -0,0 +1,147 @@ +# Telegram AI Assistant - Setup Complete! 🎉 + +## ✅ What's Been Created + +All 5 workflows have been successfully imported into n8n: + +1. **Telegram Assistant - Receiver** - Webhook listener for Telegram messages +2. **Telegram Assistant - Router** - Command routing and dispatch +3. **Telegram Assistant - Deck Integration** - Nextcloud Deck task management +4. **Telegram Assistant - Email Search** - IMAP email search +5. **Telegram Assistant - AI Chat** - OpenAI-powered chat + +## 📍 Next Steps + +### 1. Activate Workflows in n8n + +Open https://flow.egonetix.de/ and activate each workflow: +- Click on each workflow +- Toggle the "Active" switch at the top +- Verify webhook endpoints are listening + +### 2. Configure Telegram Webhook + +You need to get your Telegram bot token and set the webhook URL: + +```bash +# Replace <YOUR_BOT_TOKEN> with your actual bot token +curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \ + -H "Content-Type: application/json" \ + -d '{"url":"https://flow.egonetix.de/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2"}' +``` + +To get your bot token: +- Open Telegram and search for @BotFather +- Send `/mybots` +- Select your bot +- Click "API Token" + +### 3. Test Your Bot + +Send these commands to your Telegram bot: + +``` +/start - Welcome message +/help - Show all commands +/deck add Review project proposal by Friday +/email search invoice +/ask What's the weather like today? +``` + +## 🔧 Credentials Used + +The workflows use these existing n8n credentials: +- **Telegram API**: `Csk5cg4HtaSqP5jJ` +- **OpenAI API**: `openai_api_key` +- **IMAP**: `BntHPR3YbFD5jAIM` + +If any credentials are missing, add them in n8n Settings → Credentials. + +## 📋 Available Commands + +### Deck Commands +- `/deck add <task>` - Add task to Nextcloud Deck + - Example: `/deck add Review Q4 reports by tomorrow` + - AI extracts: title, description, due date (supports German dates) + +### Email Commands +- `/email search <query>` - Search your inbox + - Example: `/email search invoice November` +- `/email recent` - Show 5 most recent emails + +### AI Commands +- `/ask <question>` - Ask AI anything + - Example: `/ask Explain quantum computing` + +### Other +- `/start` - Welcome message +- `/help` - Show this help + +## 🐛 Troubleshooting + +### Webhook Not Working +```bash +# Check webhook status +curl "https://api.telegram.org/bot<TOKEN>/getWebhookInfo" + +# Verify n8n workflow is active +curl https://flow.egonetix.de/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2 +``` + +### Workflows Not Executing +- Check n8n logs: `docker logs n8n` +- Verify all workflows are "Active" in n8n UI +- Check webhook endpoints are green/listening + +### Deck Card Creation Fails +```bash +# Test bash script manually +docker exec n8n /home/node/create_card_from_ai.sh "Test Task" "Description" "" +``` + +### OpenAI API Errors +- Verify API key in n8n credentials +- Check OpenAI quota/billing + +## 📁 Project Structure + +``` +/home/icke/assistant/ +├── README.md # Main documentation +├── SETUP_COMPLETE.md # This file +├── import_workflows.sh # Import script (already run) +└── workflows/ + ├── telegram-receiver.json # ✅ Imported + ├── telegram-router.json # ✅ Imported + ├── telegram-deck.json # ✅ Imported + ├── telegram-email.json # ✅ Imported + └── telegram-ai.json # ✅ Imported +``` + +## 🔐 Security Reminders + +⚠️ **Important:** +- Change n8n password from `changeme` +- Telegram bot token is sensitive - keep it secret +- Consider adding user whitelisting (only your Telegram ID) +- Enable rate limiting if bot becomes public + +## 🚀 Future Enhancements + +Ideas for expansion: +- Deck task listing and completion +- Calendar integration +- Voice message transcription +- File uploads to Nextcloud +- Scheduled reminders +- Multi-turn conversations with context + +## 📖 Full Documentation + +See [README.md](README.md) for complete technical details, architecture overview, and development guide. + +--- + +**Status:** ✅ All workflows imported and ready to activate +**Date:** 2025-12-02 +**Location:** /home/icke/assistant/ diff --git a/import_workflows.sh b/import_workflows.sh new file mode 100755 index 0000000..f94f204 --- /dev/null +++ b/import_workflows.sh @@ -0,0 +1,101 @@ +#!/bin/bash +############################################################################### +# n8n Workflow Import Script +# +# Purpose: Import all Telegram Assistant workflows into n8n via API +# Usage: ./import_workflows.sh +############################################################################### + +set +e + +echo "🚀 Importing Telegram Assistant workflows into n8n" +echo "==================================================" + +# Configuration +N8N_URL="https://flow.egonetix.de" +N8N_API_KEY="n8n_api_42f1838c1e2de90cadcb669f78083de92697a85322c0b6009ad2e55760db992ab0bf61515a3cf0e1" +WORKFLOW_DIR="/home/icke/assistant/workflows" + +# Workflows to import (in order) +WORKFLOWS=( + "telegram-receiver.json" + "telegram-router.json" + "telegram-deck.json" + "telegram-email.json" + "telegram-ai.json" +) + +# Function to import a single workflow +import_workflow() { + local workflow_file="$1" + local workflow_path="$WORKFLOW_DIR/$workflow_file" + + if [ ! -f "$workflow_path" ]; then + echo "❌ Workflow file not found: $workflow_path" + return 1 + fi + + echo "" + echo "📤 Importing: $workflow_file" + + # Import workflow via n8n API + response=$(curl -s -w "\n%{http_code}" -X POST "$N8N_URL/api/v1/workflows" \ + -H "X-N8N-API-KEY: $N8N_API_KEY" \ + -H "Content-Type: application/json" \ + -d @"$workflow_path") + + # Extract HTTP status code (last line) + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | head -n-1) + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + # Extract workflow ID and name from response + workflow_id=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) + workflow_name=$(echo "$body" | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4) + echo "✅ Successfully imported: $workflow_name (ID: $workflow_id)" + return 0 + else + echo "❌ Failed to import: $workflow_file" + echo " HTTP Status: $http_code" + echo " Response: $body" + return 1 + fi +} + +# Import all workflows +success_count=0 +fail_count=0 + +for workflow in "${WORKFLOWS[@]}"; do + if import_workflow "$workflow"; then + ((success_count++)) + else + ((fail_count++)) + fi +done + +# Summary +echo "" +echo "==================================================" +echo "📊 Import Summary" +echo "==================================================" +echo "✅ Successfully imported: $success_count workflows" +echo "❌ Failed: $fail_count workflows" +echo "" + +if [ $fail_count -eq 0 ]; then + echo "🎉 All workflows imported successfully!" + echo "" + echo "Next steps:" + echo "1. Open n8n: $N8N_URL" + echo "2. Activate all workflows (toggle switch in UI)" + echo "3. Configure Telegram webhook:" + echo " curl -X POST \"https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook\" \\" + echo " -H \"Content-Type: application/json\" \\" + echo " -d '{\"url\":\"$N8N_URL/webhook/8f3f59db-aaa5-4762-9416-94be04131fd2\"}'" + echo "" + echo "4. Test with: /start command in Telegram" +else + echo "⚠️ Some workflows failed to import. Check errors above." + exit 1 +fi diff --git a/workflows/deck-current.json b/workflows/deck-current.json new file mode 100644 index 0000000..dcb648c --- /dev/null +++ b/workflows/deck-current.json @@ -0,0 +1,359 @@ +{ + "name": "Telegram Assistant - Deck Integration", + "active": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "telegram-deck", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-deck-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 240, + 300 + ], + "webhookId": "telegram-deck" + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Parse deck command\nconst args = $input.item.json.args || '';\nconst chatId = $input.item.json.chatId;\n\n// Extract subcommand\nconst parts = args.trim().split(/\\s+/);\nconst subCommand = parts[0]?.toLowerCase() || 'help';\nconst taskText = parts.slice(1).join(' ');\n\nreturn {\n chatId: chatId,\n subCommand: subCommand,\n taskText: taskText,\n originalArgs: args\n};" + }, + "id": "parse-deck-command-1", + "name": "Parse Deck Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 460, + 300 + ] + }, + { + "parameters": { + "dataType": "string", + "value1": "={{ $json.subCommand }}", + "rules": { + "rules": [ + { + "value2": "add", + "output": 0 + }, + { + "value2": "list", + "output": 1 + } + ] + }, + "fallbackOutput": 2 + }, + "id": "switch-deck-action-1", + "name": "Route Deck Action", + "type": "n8n-nodes-base.switch", + "typeVersion": 3, + "position": [ + 680, + 300 + ] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.taskText }}", + "operation": "isNotEmpty" + } + ] + } + }, + "id": "check-task-text-1", + "name": "Check Task Text", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 900, + 200 + ] + }, + { + "parameters": { + "url": "https://api.openai.com/v1/chat/completions", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "openAiApi", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $credentials.openai_api_key }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Extract task information from user input. Return JSON with: title (required, concise task name), description (optional, details), duedate (optional, YYYY-MM-DD format). For German dates like 'morgen', 'übermorgen', calculate from today ({{ $now.toFormat('yyyy-MM-dd') }}). Be concise.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{ $json.taskText }}\"\n }\n ],\n \"temperature\": 0.3,\n \"max_tokens\": 150\n}", + "options": {} + }, + "id": "ai-extract-task-1", + "name": "AI Extract Task Details", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1120, + 100 + ], + "credentials": { + "httpHeaderAuth": { + "id": "openai_api_key", + "name": "OpenAI API Key" + }, + "openAiApi": { + "id": "GPzxHwwXxeZg07o5", + "name": "OpenAi account" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Parse AI response\nconst aiResponse = $input.item.json.choices[0].message.content;\nlet taskData;\n\ntry {\n // Try to parse as JSON\n taskData = JSON.parse(aiResponse);\n} catch (e) {\n // Fallback to original text\n taskData = {\n title: $input.item.json.taskText.substring(0, 100),\n description: $input.item.json.taskText,\n duedate: null\n };\n}\n\nreturn {\n chatId: $input.item.json.chatId,\n title: taskData.title || 'Untitled Task',\n description: taskData.description || '',\n duedate: taskData.duedate || null\n};" + }, + "id": "format-task-data-1", + "name": "Format Task Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1340, + 100 + ] + }, + { + "parameters": { + "command": "=/home/node/create_card_from_ai.sh \"{{ $json.title }}\" \"{{ $json.description }}\" \"{{ $json.duedate || '' }}\"" + }, + "id": "create-deck-card-1", + "name": "Create Deck Card", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 1560, + 100 + ] + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Check if card creation was successful\nconst output = $input.item.json.stdout || '';\nconst error = $input.item.json.stderr || '';\n\nlet success = false;\nlet cardUrl = '';\n\nif (output.includes('Card created') || !error) {\n success = true;\n // Extract card URL if present in output\n const urlMatch = output.match(/https?:\\/\\/[^\\s]+/);\n cardUrl = urlMatch ? urlMatch[0] : '';\n}\n\nconst responseText = success \n ? `✅ Task added to Nextcloud Deck!\\n\\n📋 *${$input.item.json.title}*${$input.item.json.duedate ? '\\n📅 Due: ' + $input.item.json.duedate : ''}${cardUrl ? '\\n🔗 ' + cardUrl : ''}`\n : `❌ Failed to create task.\\n\\nError: ${error}`;\n\nreturn {\n chatId: $input.item.json.chatId,\n text: responseText,\n parseMode: 'Markdown'\n};" + }, + "id": "format-success-response-1", + "name": "Format Success Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1780, + 100 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify($json) }}", + "options": {} + }, + "id": "respond-result-1", + "name": "Respond Result", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 2000, + 300 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"❌ Please specify a task to add.\\n\\nExample: /deck add Review Q4 reports by Friday\"\n}) }}", + "options": {} + }, + "id": "respond-no-task-1", + "name": "Respond No Task", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 1120, + 300 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"📋 *Deck Commands*\\n\\n/deck add <task> - Add a task\\n/deck list - List tasks (coming soon)\\n\\nExample:\\n/deck add Review project proposal by tomorrow\"\n}) }}", + "options": {} + }, + "id": "respond-deck-help-1", + "name": "Respond Deck Help", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 900, + 580 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"📋 List functionality coming soon!\\n\\nFor now, check your Nextcloud Deck directly at:\\nhttps://nextcloud.egonetix.de/apps/deck\"\n}) }}", + "options": {} + }, + "id": "respond-list-placeholder-1", + "name": "Respond List (Coming Soon)", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 900, + 400 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Parse Deck Command", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Deck Command": { + "main": [ + [ + { + "node": "Route Deck Action", + "type": "main", + "index": 0 + } + ] + ] + }, + "Route Deck Action": { + "main": [ + [ + { + "node": "Check Task Text", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check Task Text": { + "main": [ + [ + { + "node": "AI Extract Task Details", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Respond No Task", + "type": "main", + "index": 0 + } + ] + ] + }, + "AI Extract Task Details": { + "main": [ + [ + { + "node": "Format Task Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Task Data": { + "main": [ + [ + { + "node": "Create Deck Card", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create Deck Card": { + "main": [ + [ + { + "node": "Format Success Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Success Response": { + "main": [ + [ + { + "node": "Respond Result", + "type": "main", + "index": 0 + } + ] + ] + }, + "Respond No Task": { + "main": [ + [ + { + "node": "Respond Result", + "type": "main", + "index": 0 + } + ] + ] + }, + "Respond Deck Help": { + "main": [ + [ + { + "node": "Respond Result", + "type": "main", + "index": 0 + } + ] + ] + }, + "Respond List (Coming Soon)": { + "main": [ + [ + { + "node": "Respond Result", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": {}, + "staticData": null, + "meta": null, + "pinData": {}, + "triggerCount": 0, + "tags": [] +} diff --git a/workflows/deck-fixed.json b/workflows/deck-fixed.json new file mode 100644 index 0000000..22439d5 --- /dev/null +++ b/workflows/deck-fixed.json @@ -0,0 +1 @@ +{"name": "Telegram Assistant - Deck Integration", "active": false, "nodes": [{"parameters": {"httpMethod": "POST", "path": "telegram-deck", "responseMode": "responseNode", "options": {}}, "id": "webhook-deck-1", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 1.1, "position": [240, 300], "webhookId": "telegram-deck"}, {"parameters": {"mode": "runOnceForEachItem", "jsCode": "// Parse deck command\nconst args = $input.item.json.args || '';\nconst chatId = $input.item.json.chatId;\n\n// Extract subcommand\nconst parts = args.trim().split(/\\s+/);\nconst subCommand = parts[0]?.toLowerCase() || 'help';\nconst taskText = parts.slice(1).join(' ');\n\nreturn {\n chatId: chatId,\n subCommand: subCommand,\n taskText: taskText,\n originalArgs: args\n};"}, "id": "parse-deck-command-1", "name": "Parse Deck Command", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [460, 300]}, {"parameters": {"dataType": "string", "value1": "={{ $json.subCommand }}", "rules": {"rules": [{"value2": "add", "output": 0}, {"value2": "list", "output": 1}]}, "fallbackOutput": 2}, "id": "switch-deck-action-1", "name": "Route Deck Action", "type": "n8n-nodes-base.switch", "typeVersion": 3, "position": [680, 300]}, {"parameters": {"conditions": {"string": [{"value1": "={{ $json.taskText }}", "operation": "isNotEmpty"}]}}, "id": "check-task-text-1", "name": "Check Task Text", "type": "n8n-nodes-base.if", "typeVersion": 1, "position": [900, 200]}, {"parameters": {"url": "https://api.openai.com/v1/chat/completions", "authentication": "predefinedCredentialType", "nodeCredentialType": "openAiApi", "sendHeaders": true, "headerParameters": {"parameters": [{"name": "Authorization", "value": "=Bearer {{ $credentials.openai_api_key }}"}]}, "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Extract task information from user input. Return JSON with: title (required, concise task name), description (optional, details), duedate (optional, YYYY-MM-DD format). For German dates like 'morgen', '\u00fcbermorgen', calculate from today ({{ $now.toFormat('yyyy-MM-dd') }}). Be concise.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{ $json.taskText }}\"\n }\n ],\n \"temperature\": 0.3,\n \"max_tokens\": 150\n}", "options": {}}, "id": "ai-extract-task-1", "name": "AI Extract Task Details", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [1120, 100], "credentials": {"httpHeaderAuth": {"id": "openai_api_key", "name": "OpenAI API Key"}}}, {"parameters": {"mode": "runOnceForEachItem", "jsCode": "// Parse AI response\nconst aiResponse = $input.item.json.choices[0].message.content;\nlet taskData;\n\ntry {\n // Try to parse as JSON\n taskData = JSON.parse(aiResponse);\n} catch (e) {\n // Fallback to original text\n taskData = {\n title: $input.item.json.taskText.substring(0, 100),\n description: $input.item.json.taskText,\n duedate: null\n };\n}\n\nreturn {\n chatId: $input.item.json.chatId,\n title: taskData.title || 'Untitled Task',\n description: taskData.description || '',\n duedate: taskData.duedate || null\n};"}, "id": "format-task-data-1", "name": "Format Task Data", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1340, 100]}, {"parameters": {"command": "=/home/node/create_card_from_ai.sh \"{{ $json.title }}\" \"{{ $json.description }}\" \"{{ $json.duedate || '' }}\""}, "id": "create-deck-card-1", "name": "Create Deck Card", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [1560, 100]}, {"parameters": {"mode": "runOnceForEachItem", "jsCode": "// Check if card creation was successful\nconst output = $input.item.json.stdout || '';\nconst error = $input.item.json.stderr || '';\n\nlet success = false;\nlet cardUrl = '';\n\nif (output.includes('Card created') || !error) {\n success = true;\n // Extract card URL if present in output\n const urlMatch = output.match(/https?:\\/\\/[^\\s]+/);\n cardUrl = urlMatch ? urlMatch[0] : '';\n}\n\nconst responseText = success \n ? `\u2705 Task added to Nextcloud Deck!\\n\\n\ud83d\udccb *${$input.item.json.title}*${$input.item.json.duedate ? '\\n\ud83d\udcc5 Due: ' + $input.item.json.duedate : ''}${cardUrl ? '\\n\ud83d\udd17 ' + cardUrl : ''}`\n : `\u274c Failed to create task.\\n\\nError: ${error}`;\n\nreturn {\n chatId: $input.item.json.chatId,\n text: responseText,\n parseMode: 'Markdown'\n};"}, "id": "format-success-response-1", "name": "Format Success Response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1780, 100]}, {"parameters": {"respondWith": "json", "responseBody": "={{ JSON.stringify($json) }}", "options": {}}, "id": "respond-result-1", "name": "Respond Result", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [2000, 300]}, {"parameters": {"respondWith": "json", "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"\u274c Please specify a task to add.\\n\\nExample: /deck add Review Q4 reports by Friday\"\n}) }}", "options": {}}, "id": "respond-no-task-1", "name": "Respond No Task", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [1120, 300]}, {"parameters": {"respondWith": "json", "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"\ud83d\udccb *Deck Commands*\\n\\n/deck add <task> - Add a task\\n/deck list - List tasks (coming soon)\\n\\nExample:\\n/deck add Review project proposal by tomorrow\"\n}) }}", "options": {}}, "id": "respond-deck-help-1", "name": "Respond Deck Help", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [900, 580]}, {"parameters": {"respondWith": "json", "responseBody": "={{ JSON.stringify({\n chatId: $json.chatId,\n text: \"\ud83d\udccb List functionality coming soon!\\n\\nFor now, check your Nextcloud Deck directly at:\\nhttps://nextcloud.egonetix.de/apps/deck\"\n}) }}", "options": {}}, "id": "respond-list-placeholder-1", "name": "Respond List (Coming Soon)", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [900, 400]}], "connections": {"Webhook": {"main": [[{"node": "Parse Deck Command", "type": "main", "index": 0}]]}, "Parse Deck Command": {"main": [[{"node": "Route Deck Action", "type": "main", "index": 0}]]}, "Route Deck Action": {"main": [[{"node": "Check Task Text", "type": "main", "index": 0}]]}, "Check Task Text": {"main": [[{"node": "AI Extract Task Details", "type": "main", "index": 0}], [{"node": "Respond No Task", "type": "main", "index": 0}]]}, "AI Extract Task Details": {"main": [[{"node": "Format Task Data", "type": "main", "index": 0}]]}, "Format Task Data": {"main": [[{"node": "Create Deck Card", "type": "main", "index": 0}]]}, "Create Deck Card": {"main": [[{"node": "Format Success Response", "type": "main", "index": 0}]]}, "Format Success Response": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, "Respond No Task": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, "Respond Deck Help": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, "Respond List (Coming Soon)": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}}, "settings": {}, "staticData": null, "meta": null, "pinData": {}, "triggerCount": 0, "tags": []} \ No newline at end of file diff --git a/workflows/telegram-ai.json b/workflows/telegram-ai.json new file mode 100644 index 0000000..9185531 --- /dev/null +++ b/workflows/telegram-ai.json @@ -0,0 +1,112 @@ +{ + "name": "Telegram Assistant - AI Chat", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "4e0e30c5-2360-49e5-a37c-e2558f811341", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-ai-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 240, + 300 + ], + "webhookId": "4e0e30c5-2360-49e5-a37c-e2558f811341" + }, + { + "parameters": { + "method": "POST", + "url": "https://api.openai.com/v1/chat/completions", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "openAiApi", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"{{ $json.body.args }}\"\n }\n ],\n \"temperature\": 0.7,\n \"max_tokens\": 500\n}", + "options": {} + }, + "id": "call-openai-1", + "name": "Call OpenAI", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 460, + 300 + ], + "credentials": { + "openAiApi": { + "id": "openai_api_key", + "name": "OpenAI API Key" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const aiResponse = $input.item.json.choices[0].message.content;\nconst chatId = $input.item.json.body.chatId;\n\nreturn {\n chatId: chatId,\n text: aiResponse,\n parseMode: 'Markdown'\n};" + }, + "id": "format-response-1", + "name": "Format Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 680, + 300 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify($json) }}" + }, + "id": "respond-1", + "name": "Respond", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 900, + 300 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Call OpenAI", + "type": "main", + "index": 0 + } + ] + ] + }, + "Call OpenAI": { + "main": [ + [ + { + "node": "Format Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Response": { + "main": [ + [ + { + "node": "Respond", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": {} +} \ No newline at end of file diff --git a/workflows/telegram-deck.json b/workflows/telegram-deck.json new file mode 100644 index 0000000..897d38c --- /dev/null +++ b/workflows/telegram-deck.json @@ -0,0 +1,179 @@ +{ + "name": "Telegram Assistant - Deck Integration", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "telegram-deck", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-deck-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [240, 300], + "webhookId": "telegram-deck" + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const args = $input.item.json.args || '';\nconst chatId = $input.item.json.chatId;\nconst parts = args.trim().split(/\\s+/);\nconst subCommand = parts[0]?.toLowerCase() || 'help';\nconst taskText = parts.slice(1).join(' ');\nreturn {chatId, subCommand, taskText, originalArgs: args};" + }, + "id": "parse-deck-command-1", + "name": "Parse Deck Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [460, 300] + }, + { + "parameters": { + "mode": "rules", + "rules": { + "rules": [ + { + "operation": "equal", + "value1": "={{ $json.subCommand }}", + "value2": "add" + }, + { + "operation": "equal", + "value1": "={{ $json.subCommand }}", + "value2": "list" + } + ] + }, + "fallbackOutput": "extra" + }, + "id": "switch-deck-action-1", + "name": "Route Deck Action", + "type": "n8n-nodes-base.switch", + "typeVersion": 1, + "position": [680, 300] + }, + { + "parameters": { + "conditions": { + "string": [{"value1": "={{ $json.taskText }}", "operation": "isNotEmpty"}] + } + }, + "id": "check-task-text-1", + "name": "Check Task Text", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [900, 200] + }, + { + "parameters": { + "url": "https://api.openai.com/v1/chat/completions", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\"model\": \"gpt-4o-mini\", \"messages\": [{\"role\": \"system\", \"content\": \"Extract task information from user input. Return JSON with: title (required, concise task name), description (optional, details), duedate (optional, YYYY-MM-DD format). For German dates like 'morgen', 'übermorgen', calculate from today ({{ $now.toFormat('yyyy-MM-dd') }}). Be concise.\"}, {\"role\": \"user\", \"content\": \"{{ $json.taskText }}\"}], \"temperature\": 0.3, \"max_tokens\": 150}", + "options": {} + }, + "id": "ai-extract-task-1", + "name": "AI Extract Task Details", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [1120, 100], + "credentials": { + "httpHeaderAuth": { + "id": "openai_api_key", + "name": "OpenAI API Key" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const aiResponse = $input.item.json.choices[0].message.content;\nlet taskData;\ntry {taskData = JSON.parse(aiResponse);} catch (e) {taskData = {title: $input.item.json.taskText.substring(0, 100), description: $input.item.json.taskText, duedate: null};}\nreturn {chatId: $input.item.json.chatId, title: taskData.title || 'Untitled Task', description: taskData.description || '', duedate: taskData.duedate || null};" + }, + "id": "format-task-data-1", + "name": "Format Task Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1340, 100] + }, + { + "parameters": { + "command": "=/home/node/create_card_from_ai.sh \"{{ $json.title }}\" \"{{ $json.description }}\" \"{{ $json.duedate || '' }}\"" + }, + "id": "create-deck-card-1", + "name": "Create Deck Card", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [1560, 100] + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const output = $input.item.json.stdout || '';\nconst error = $input.item.json.stderr || '';\nlet success = !error || output.includes('Card created');\nconst urlMatch = output.match(/https?:\\/\\/[^\\s]+/);\nconst cardUrl = urlMatch ? urlMatch[0] : '';\nconst responseText = success ? `✅ Task added to Nextcloud Deck!\\n\\n📋 *${$input.item.json.title}*${$input.item.json.duedate ? '\\n📅 Due: ' + $input.item.json.duedate : ''}${cardUrl ? '\\n🔗 ' + cardUrl : ''}` : `❌ Failed to create task.\\n\\nError: ${error}`;\nreturn {chatId: $input.item.json.chatId, text: responseText, parseMode: 'Markdown'};" + }, + "id": "format-success-response-1", + "name": "Format Success Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1780, 100] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify($json) }}" + }, + "id": "respond-result-1", + "name": "Respond Result", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [2000, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"❌ Please specify a task to add.\\n\\nExample: /deck add Review Q4 reports by Friday\"}) }}" + }, + "id": "respond-no-task-1", + "name": "Respond No Task", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1120, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"📋 *Deck Commands*\\n\\n/deck add <task>\\nOr: 'add a deck called cleaning'\\n\\nExample:\\n/deck add Review project proposal by tomorrow\", parseMode: 'Markdown'}) }}" + }, + "id": "respond-deck-help-1", + "name": "Respond Deck Help", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [900, 500] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"📋 List functionality coming soon!\\n\\nFor now, check your Nextcloud Deck at:\\nhttps://nextcloud.egonetix.de/apps/deck\"}) }}" + }, + "id": "respond-list-placeholder-1", + "name": "Respond List", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [900, 400] + } + ], + "connections": { + "Webhook": {"main": [[{"node": "Parse Deck Command", "type": "main", "index": 0}]]}, + "Parse Deck Command": {"main": [[{"node": "Route Deck Action", "type": "main", "index": 0}]]}, + "Route Deck Action": {"main": [[{"node": "Check Task Text", "type": "main", "index": 0}], [{"node": "Respond List", "type": "main", "index": 0}], [{"node": "Respond Deck Help", "type": "main", "index": 0}]]}, + "Check Task Text": {"main": [[{"node": "AI Extract Task Details", "type": "main", "index": 0}], [{"node": "Respond No Task", "type": "main", "index": 0}]]}, + "AI Extract Task Details": {"main": [[{"node": "Format Task Data", "type": "main", "index": 0}]]}, + "Format Task Data": {"main": [[{"node": "Create Deck Card", "type": "main", "index": 0}]]}, + "Create Deck Card": {"main": [[{"node": "Format Success Response", "type": "main", "index": 0}]]}, + "Format Success Response": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, + "Respond No Task": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, + "Respond Deck Help": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, + "Respond List": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]} + }, + "settings": {} +} diff --git a/workflows/telegram-email.json b/workflows/telegram-email.json new file mode 100644 index 0000000..592cc89 --- /dev/null +++ b/workflows/telegram-email.json @@ -0,0 +1,157 @@ +{ + "name": "Telegram Assistant - Email Search", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "telegram-email", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-email-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [240, 300], + "webhookId": "telegram-email" + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const args = $input.item.json.args || '';\nconst chatId = $input.item.json.chatId;\nconst parts = args.trim().split(/\\s+/);\nconst subCommand = parts[0]?.toLowerCase() || 'help';\nconst searchQuery = parts.slice(1).join(' ');\nreturn {chatId, subCommand, searchQuery, originalArgs: args};" + }, + "id": "parse-email-command-1", + "name": "Parse Email Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [460, 300] + }, + { + "parameters": { + "mode": "rules", + "rules": { + "rules": [ + { + "operation": "equal", + "value1": "={{ $json.subCommand }}", + "value2": "search" + }, + { + "operation": "equal", + "value1": "={{ $json.subCommand }}", + "value2": "recent" + } + ] + }, + "fallbackOutput": "extra" + }, + "id": "switch-email-action-1", + "name": "Route Email Action", + "type": "n8n-nodes-base.switch", + "typeVersion": 1, + "position": [680, 300] + }, + { + "parameters": { + "conditions": { + "string": [{"value1": "={{ $json.searchQuery }}", "operation": "isNotEmpty"}] + } + }, + "id": "check-search-query-1", + "name": "Check Search Query", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [900, 200] + }, + { + "parameters": { + "mailbox": "INBOX", + "options": {"limit": 5, "search": "={{ $json.searchQuery }}"} + }, + "id": "imap-search-1", + "name": "IMAP Search", + "type": "n8n-nodes-base.emailReadImap", + "typeVersion": 2, + "position": [1120, 100], + "credentials": { + "imap": { + "id": "BntHPR3YbFD5jAIM", + "name": "IMAP account" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "const chatId = $input.first().json.chatId;\nconst emails = $input.all();\nif (emails.length === 0) {return {chatId, text: \"📭 No emails found matching your search.\", parseMode: 'Markdown'};}\nlet responseText = `📧 *Found ${emails.length} email${emails.length > 1 ? 's' : ''}:*\\n\\n`;\nemails.forEach((email, index) => {\n const subject = email.json.subject || '(No subject)';\n const from = email.json.from?.text || 'Unknown';\n const date = email.json.date ? new Date(email.json.date).toLocaleDateString('de-DE') : 'Unknown';\n responseText += `${index + 1}. *${subject}*\\n From: ${from}\\n Date: ${date}\\n\\n`;\n});\nif (emails.length === 5) {responseText += \"_Showing first 5 results. Refine your search for more specific results._\";}\nreturn {chatId, text: responseText, parseMode: 'Markdown'};" + }, + "id": "format-email-results-1", + "name": "Format Email Results", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1340, 100] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify($json) }}" + }, + "id": "respond-result-1", + "name": "Respond Result", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1560, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"❌ Please specify what to search for.\\n\\nExample: /email search invoice November\"}) }}" + }, + "id": "respond-no-query-1", + "name": "Respond No Query", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1120, 300] + }, + { + "parameters": { + "mailbox": "INBOX", + "options": {"limit": 5} + }, + "id": "imap-recent-1", + "name": "IMAP Recent", + "type": "n8n-nodes-base.emailReadImap", + "typeVersion": 2, + "position": [900, 400], + "credentials": { + "imap": { + "id": "BntHPR3YbFD5jAIM", + "name": "IMAP account" + } + } + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"📧 *Email Commands*\\n\\n/email search <query>\\nOr: 'find my train ticket'\\n\\nExamples:\\n/email search invoice from last week\\n/email recent\", parseMode: 'Markdown'}) }}" + }, + "id": "respond-email-help-1", + "name": "Respond Email Help", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [900, 500] + } + ], + "connections": { + "Webhook": {"main": [[{"node": "Parse Email Command", "type": "main", "index": 0}]]}, + "Parse Email Command": {"main": [[{"node": "Route Email Action", "type": "main", "index": 0}]]}, + "Route Email Action": {"main": [[{"node": "Check Search Query", "type": "main", "index": 0}], [{"node": "IMAP Recent", "type": "main", "index": 0}], [{"node": "Respond Email Help", "type": "main", "index": 0}]]}, + "Check Search Query": {"main": [[{"node": "IMAP Search", "type": "main", "index": 0}], [{"node": "Respond No Query", "type": "main", "index": 0}]]}, + "IMAP Search": {"main": [[{"node": "Format Email Results", "type": "main", "index": 0}]]}, + "Format Email Results": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, + "Respond No Query": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]}, + "IMAP Recent": {"main": [[{"node": "Format Email Results", "type": "main", "index": 0}]]}, + "Respond Email Help": {"main": [[{"node": "Respond Result", "type": "main", "index": 0}]]} + }, + "settings": {} +} diff --git a/workflows/telegram-receiver.json b/workflows/telegram-receiver.json new file mode 100644 index 0000000..e6217de --- /dev/null +++ b/workflows/telegram-receiver.json @@ -0,0 +1,275 @@ +{ + "name": "Telegram Assistant - Receiver", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "8f3f59db-aaa5-4762-9416-94be04131fd2", + "options": {} + }, + "id": "telegram-trigger-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 240, + 300 + ], + "webhookId": "8f3f59db-aaa5-4762-9416-94be04131fd2" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{$json.message?.text || ''}}", + "operation": "startsWith", + "value2": "/" + } + ] + } + }, + "id": "check-command-1", + "name": "Check if Command", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 460, + 300 + ] + }, + { + "parameters": { + "chatId": "={{ $json.message.chat.id }}", + "text": "=Command received: {{ $json.message.text }}\n\nProcessing...", + "additionalFields": {} + }, + "id": "send-ack-1", + "name": "Send Acknowledgment", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [ + 680, + 200 + ], + "credentials": { + "telegramApi": { + "id": "6Rim8odDFpQoHxCM", + "name": "mortimer" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Extract command and parameters\nconst text = $input.item.json.message?.text || '';\nconst chatId = $input.item.json.message?.chat?.id;\nconst messageId = $input.item.json.message?.message_id;\nconst userId = $input.item.json.message?.from?.id;\nconst username = $input.item.json.message?.from?.username || 'unknown';\n\n// Parse command\nconst parts = text.trim().split(/\\s+/);\nconst command = parts[0].toLowerCase();\nconst args = parts.slice(1).join(' ');\n\nreturn {\n command: command,\n args: args,\n chatId: chatId,\n messageId: messageId,\n userId: userId,\n username: username,\n originalText: text\n};" + }, + "id": "parse-command-1", + "name": "Parse Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 900, + 200 + ] + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Extract natural language message\nconst text = $input.item.json.message?.text || '';\nconst chatId = $input.item.json.message?.chat?.id;\nconst messageId = $input.item.json.message?.message_id;\nconst userId = $input.item.json.message?.from?.id;\nconst username = $input.item.json.message?.from?.username || 'unknown';\n\nreturn {\n chatId: chatId,\n messageId: messageId,\n userId: userId,\n username: username,\n originalText: text\n};" + }, + "id": "parse-natural-language-1", + "name": "Parse Natural Language", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 680, + 400 + ] + }, + { + "parameters": { + "method": "POST", + "url": "https://api.openai.com/v1/chat/completions", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "openAiApi", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $credentials.openai_api_key }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Analyze user intent and classify into one of these categories. Return ONLY valid JSON with no additional text:\\n\\n1. 'deck_add' - User wants to create a task/card in Nextcloud Deck\\n Examples: 'add a deck called cleaning', 'create task review reports', 'remind me to call mom tomorrow'\\n Extract: {intent: 'deck_add', task: 'task description', duedate: 'YYYY-MM-DD or null'}\\n\\n2. 'email_search' - User wants to search emails\\n Examples: 'find my train ticket', 'search for invoice', 'show emails from last week'\\n Extract: {intent: 'email_search', query: 'search terms'}\\n\\n3. 'general_chat' - General questions, conversation\\n Examples: 'what's the weather', 'explain quantum physics', 'tell me a joke'\\n Extract: {intent: 'general_chat', question: 'full question'}\\n\\nFor dates like 'today', 'tomorrow', 'next week', calculate from today: {{ $now.toFormat('yyyy-MM-dd') }}.\\nFor German: 'morgen' = tomorrow, 'übermorgen' = day after tomorrow.\\n\\nReturn JSON format: {\\\"intent\\\": \\\"category\\\", ...extracted_data}\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{ $json.originalText }}\"\n }\n ],\n \"temperature\": 0.3,\n \"max_tokens\": 150\n}", + "options": {} + }, + "id": "ai-classify-intent-1", + "name": "AI Classify Intent", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 900, + 400 + ], + "credentials": { + "httpHeaderAuth": { + "id": "openai_api_key", + "name": "OpenAI API Key" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "// Parse AI classification result\nconst aiResponse = $input.item.json.choices[0].message.content;\nconst chatId = $input.item.json.chatId;\nconst userId = $input.item.json.userId;\nconst username = $input.item.json.username;\nconst originalText = $input.item.json.originalText;\n\nlet classification;\ntry {\n classification = JSON.parse(aiResponse);\n} catch (e) {\n // Fallback to general chat if parsing fails\n classification = {\n intent: 'general_chat',\n question: originalText\n };\n}\n\n// Map intent to command format for router\nlet command, args;\n\nswitch (classification.intent) {\n case 'deck_add':\n command = '/deck';\n args = 'add ' + (classification.task || originalText);\n break;\n case 'email_search':\n command = '/email';\n args = 'search ' + (classification.query || originalText);\n break;\n case 'general_chat':\n default:\n command = '/ask';\n args = classification.question || originalText;\n break;\n}\n\nreturn {\n command: command,\n args: args,\n chatId: chatId,\n userId: userId,\n username: username,\n originalText: originalText,\n classifiedIntent: classification.intent\n};" + }, + "id": "format-as-command-1", + "name": "Format as Command", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1120, + 400 + ] + }, + { + "parameters": { + "method": "POST", + "url": "=https://flow.egonetix.de/webhook/telegram-router-direct", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": { + "response": { + "response": { + "neverError": true + } + } + } + }, + "id": "call-router-natural-1", + "name": "Call Router (Natural Language)", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 1340, + 400 + ] + }, + { + "parameters": { + "method": "POST", + "url": "=https://flow.egonetix.de/webhook/telegram-router-direct", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": { + "response": { + "response": { + "neverError": true + } + } + } + }, + "id": "call-router-1", + "name": "Call Router Workflow", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 1120, + 200 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Check if Command", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check if Command": { + "main": [ + [ + { + "node": "Send Acknowledgment", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Parse Natural Language", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Natural Language": { + "main": [ + [ + { + "node": "AI Classify Intent", + "type": "main", + "index": 0 + } + ] + ] + }, + "AI Classify Intent": { + "main": [ + [ + { + "node": "Format as Command", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format as Command": { + "main": [ + [ + { + "node": "Call Router (Natural Language)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Send Acknowledgment": { + "main": [ + [ + { + "node": "Parse Command", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Command": { + "main": [ + [ + { + "node": "Call Router Workflow", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": {} +} diff --git a/workflows/telegram-router.json b/workflows/telegram-router.json new file mode 100644 index 0000000..5558ef2 --- /dev/null +++ b/workflows/telegram-router.json @@ -0,0 +1,175 @@ +{ + "name": "Telegram Assistant - Router", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "79209f6f-28f3-4782-8366-2517e47adc0e", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-router-1", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [240, 300], + "webhookId": "79209f6f-28f3-4782-8366-2517e47adc0e" + }, + { + "parameters": { + "mode": "rules", + "rules": { + "rules": [ + { + "operation": "equal", + "value1": "={{ $json.command }}", + "value2": "/start" + }, + { + "operation": "equal", + "value1": "={{ $json.command }}", + "value2": "/help" + }, + { + "operation": "equal", + "value1": "={{ $json.command }}", + "value2": "/deck" + }, + { + "operation": "equal", + "value1": "={{ $json.command }}", + "value2": "/email" + }, + { + "operation": "equal", + "value1": "={{ $json.command }}", + "value2": "/ask" + } + ] + }, + "fallbackOutput": "extra" + }, + "id": "switch-command-1", + "name": "Route Command", + "type": "n8n-nodes-base.switch", + "typeVersion": 1, + "position": [460, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"👋 Welcome to your Telegram Assistant!\\n\\nI can help you with:\\n\\n📋 /deck - Manage Nextcloud Deck tasks\\n📧 /email - Search your emails\\n💬 /ask - Chat with AI\\n❓ /help - Show this message\\n\\nOr just chat naturally - I understand!\", parseMode: 'Markdown'}) }}" + }, + "id": "respond-start-1", + "name": "Respond Start", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [680, 100] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"🤖 *Telegram Assistant Help*\\n\\n📋 *Deck:*\\n /deck add <task>\\n Or: 'add a deck called cleaning'\\n\\n📧 *Email:*\\n /email search <query>\\n Or: 'find my train ticket'\\n\\n💬 *AI:*\\n /ask <question>\\n Or: 'what's the weather?'\", parseMode: 'Markdown'}) }}" + }, + "id": "respond-help-1", + "name": "Respond Help", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [680, 200] + }, + { + "parameters": { + "url": "=https://flow.egonetix.de/webhook/c1294640-f15e-411e-b38b-8b69b8419bce", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": {} + }, + "id": "call-deck-1", + "name": "Call Deck Workflow", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [680, 300] + }, + { + "parameters": { + "url": "=https://flow.egonetix.de/webhook/telegram-email", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": {} + }, + "id": "call-email-1", + "name": "Call Email Workflow", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [680, 400] + }, + { + "parameters": { + "url": "=https://flow.egonetix.de/webhook/4e0e30c5-2360-49e5-a37c-e2558f811341", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": {} + }, + "id": "call-ai-1", + "name": "Call AI Workflow", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [680, 500] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({chatId: $json.chatId, text: \"❌ Unknown command: \" + $json.command + \"\\n\\nType /help to see available commands.\"}) }}" + }, + "id": "respond-unknown-1", + "name": "Respond Unknown", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [680, 600] + }, + { + "parameters": { + "chatId": "={{ $json.chatId }}", + "text": "={{ $json.text }}", + "additionalFields": {"parse_mode": "={{ $json.parseMode || 'HTML' }}"} + }, + "id": "send-telegram-1", + "name": "Send Telegram Response", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [900, 300], + "credentials": { + "telegramApi": { + "id": "6Rim8odDFpQoHxCM", + "name": "mortimer" + } + } + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({ success: true }) }}" + }, + "id": "respond-ok-1", + "name": "Respond OK", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1120, 300] + } + ], + "connections": { + "Webhook": {"main": [[{"node": "Route Command", "type": "main", "index": 0}]]}, + "Route Command": {"main": [[{"node": "Respond Start", "type": "main", "index": 0}], [{"node": "Respond Help", "type": "main", "index": 0}], [{"node": "Call Deck Workflow", "type": "main", "index": 0}], [{"node": "Call Email Workflow", "type": "main", "index": 0}], [{"node": "Call AI Workflow", "type": "main", "index": 0}], [{"node": "Respond Unknown", "type": "main", "index": 0}]]}, + "Respond Start": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Respond Help": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Call Deck Workflow": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Call Email Workflow": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Call AI Workflow": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Respond Unknown": {"main": [[{"node": "Send Telegram Response", "type": "main", "index": 0}]]}, + "Send Telegram Response": {"main": [[{"node": "Respond OK", "type": "main", "index": 0}]]} + }, + "settings": {} +}