# Nextcloud Talk Bot Integration Inara is registered as a bot in Nextcloud Talk, receiving messages via webhook and replying through the bot API. --- ## Installation Run on the Nextcloud server (inside the Docker container): ```bash docker exec -it --user www-data php /var/www/html/occ talk:bot:install \ "Inara" \ "" \ "https://cortex.dgrzone.com/inara-nextcloud-talk-webhook" \ --feature webhook --feature response --feature reaction ``` After installing, enable the bot in each Talk conversation via the conversation settings UI. --- ## Configuration (cortex/.env) ``` NEXTCLOUD_TALK_BOT_SECRET= ``` `NEXTCLOUD_URL` defaults to `https://cloud.dgrzone.com` in `config.py`. --- ## How It Works 1. User sends a message in Talk → Nextcloud POSTs signed webhook to `/inara-nextcloud-talk-webhook` 2. Cortex verifies the incoming HMAC signature, extracts the message, runs it through the LLM 3. Cortex POSTs the reply to `/ocs/v2.php/apps/spreed/api/v1/bot/{token}/message` --- ## HMAC Signing — Critical Detail **The signature covers `random + message_text`, NOT `random + raw_body`.** This differs from typical webhook protocols. Nextcloud Talk's `BotController::sendMessage` passes the *parsed `$message` parameter* to `ChecksumVerificationService::validateRequest`, not the raw request body. Source: `spreed/lib/Controller/BotController.php` → `getBotFromHeaders()`: ```php $this->checksumVerificationService->validateRequest($random, $checksum, $secret, $message); // $message is the parsed string, not $request->getContent() ``` Correct Python: ```python sig = hmac.new( secret.encode(), (random_str + message_text).encode("utf-8"), # message_text only, not full body hashlib.sha256, ).hexdigest() ``` Wrong (causes persistent 401): ```python # DON'T sign the full JSON body: sig = hmac.new(secret.encode(), (random_str + '{"message": "..."}').encode(), hashlib.sha256).hexdigest() ``` --- ## Claude CLI Auth in systemd The `CLAUDE_CODE_OAUTH_TOKEN` in `.env` goes stale after each `claude auth login` (tokens rotate). Cortex reads the token live from `~/.claude/.credentials.json` on every Claude call (`llm_client._fresh_claude_token()`), so no manual `.env` update is needed after re-authentication. Also: never set `ANTHROPIC_API_KEY` to an OAuth token value (`sk-ant-oat01-...`) — the Claude CLI treats it as a direct API key and fails. Only real API keys (`sk-ant-api03-...`) belong in `ANTHROPIC_API_KEY`. --- ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| | Webhook not received | Bot not enabled for conversation | Enable in Talk conversation settings | | Incoming 401 on webhook | Wrong secret in `.env` | Match secret to `occ talk:bot:install` value | | Reply POST returns 401 | HMAC computed over wrong data | Sign `random + message_text` only | | Reply POST returns 401 (persistent) | Brute force protection | `occ security:bruteforce:reset ` | | Claude falls back to Gemini | Stale/wrong auth token | Token is auto-refreshed from `~/.claude/.credentials.json`; run `claude auth login` if expired | | Bot auto-disabled by Nextcloud | Webhook held open too long during LLM call | Use `BackgroundTasks` — return 200 immediately |