diff --git a/Future to-do.md b/Future to-do.md deleted file mode 100644 index 1bdc1af..0000000 --- a/Future to-do.md +++ /dev/null @@ -1,180 +0,0 @@ -# Future To-Do - -Planned features, known limitations, and architectural notes for nc_bot_webhooks. - ---- - -## Inbound Messages (apprise-request) - -**Status:** Planning phase - -### Concept - -Add a new endpoint that polls Nextcloud Talk for new messages, returning them in an Apprise-compatible JSON format so external services can read from the chat and trigger actions. - -``` -POST /apps/nc_bot_webhooks/apprise-request//notify/ -``` - -### Request - -```json -{ - "last-seen": 42 -} -``` - -### Response - -```json -{ - "43": { - "version": 0, - "subject": "Alice", - "title": "Message from Alice", - "body": "Hello from the other side!", - "type": "info" - }, - "44": { - "version": 0, - "subject": "Bob", - "title": "Message from Bob", - "body": "Welcome back!", - "type": "info" - } -} -``` - -Each key is the Talk message ID. The keys are always in ascending order by ID. The client finds the largest key for the next request: `Math.max(...Object.keys(response))`. - -> **Note:** JSON objects are technically unordered per spec, but all major implementations preserve insertion order. If strict ordering is required by a client, it can use `Object.keys(response).sort((a, b) => Number(a))` to guarantee sort. - -### Design Decision: Message ID vs Timestamp - -Use **message ID** (integer), not a timestamp. - -Nextcloud Talk's Chat API (`GET ocs/v2.php/apps/spreed/api/v1/chat/{roomToken}/messages`) supports a `since` query parameter that takes a message ID. Message IDs are auto-incrementing integers, making them: - -- **Monotonically ordered** — no clock skew issues between Talk server and webhook caller -- **Precise** — exact message boundary, no ambiguity about which messages fall between two timestamps -- **Efficient** — Talk's DB query can use the index directly; timestamps would require a range scan -- **Consistent** — matches the existing Talk API pattern (the same `since` parameter is used for the standard polling endpoint) - -### Implementation sketch - -1. **New route** in `appinfo/routes.php`: - - `POST /apprise-request/{roomToken}/notify/{token}` - -2. **New WebhookController method** (`receiveAppriseRequest`): - - Validate auth token - - Parse `last-seen` from request body (default: 0 for first poll) - - Call `TalkService::getNewMessages($roomToken, $lastSeen)` - -3. **New TalkService method** (`getNewMessages`): - - Query Talk Chat API with `since={lastSeen}` parameter - - Parse response and map each message to an Apprise-compatible object - - Return array of Apprise-format notification objects - -4. **Admin settings UI**: - - Add a "Triggers" section for configuring inbound triggers - - Per-trigger: room selection, trigger token, filter rules (optional) - -5. **Config storage** (`appconfig`): - - New key `triggers`: JSON object mapping trigger tokens to configuration - -### Open questions - -- Should the endpoint accept an Apprise-format request (like existing webhooks) with a `last-seen` field? Or a custom format? -- Should triggers support message filtering (e.g., only messages matching a regex, only @mentions)? -- Should triggers support one-way dispatch (call an external webhook when a message matches)? -- Rate limiting: how often should external services poll? Should we add a `X-Poll-After` header? - ---- - -## Known Limitations & Fixes - -### Auth token generation is client-side -- **File:** `js/settings.js:245` -- **Issue:** Auth tokens use `btoa(Math.random() + Date.now())` — not cryptographically secure -- **Fix:** Switch to server-side `random_bytes(32)` generation -- **Priority:** Low (webhook URLs are not publicly discoverable; the threat model doesn't require it) - -### Voice message MIME type fix is disabled -- **File:** `lib/Service/TalkService.php:1556` -- **Issue:** `fixMimeTypeOfVoiceMessage()` is commented out because it creates non-voice shares for voice messages -- **Fix:** Re-enable when upgrading to a Talk version that properly filters voice message shares - -### No rate limiting on webhook endpoints -- **Issue:** Webhook endpoints have no rate limiting — vulnerable to abuse if URLs are exposed -- **Fix:** Add rate limiting via Nextcloud's `IRateLimitManager` or place behind reverse proxy - -### Bot avatar not supported -- **Issue:** NCTalk doesn't support per-message avatars; the `avatar_url` field in Discord payloads is ignored -- **Workaround:** Sender name is prepended to message text as a bold line - -### No per-message avatar support in Talk -- **Impact:** Visual identity of the sending service is conveyed via type icons + sender name, not avatars - -### Bot user requires admin privileges -- **Issue:** The `talk-bot` user must be granted admin access to list all Talk rooms -- **Impact:** Security concern — bot has more privileges than strictly necessary - -### Storage quota impact -- **Issue:** Images count toward the bot user's storage quota -- **Impact:** Large deployments with many image webhooks could exhaust the bot's quota - -### No CSRF protection on webhook endpoints -- **By design:** Webhook endpoints are marked `#[PublicPage]` + `#[NoCSRFRequired]` — auth token is the sole access control - ---- - -## Potential Future Features - -### Message filtering / pattern matching -- Allow triggers to filter incoming messages by regex, keywords, or @mention -- Enables "only notify on specific messages" rather than polling everything - -### Outbound webhook dispatch (trigger → external service) -- When a message matches a trigger, POST to an external webhook URL -- Would turn nc_bot_webhooks into a bidirectional bridge (not just a polling endpoint) - -### Slash command support -- Recognize and execute slash commands (e.g., `/status`, `/ping`) sent to the bot -- Would require the inbound message system first - -### Thread / conversation support -- Post messages within a specific thread (requires Talk thread API integration) - -### Rich object support expansion -- Currently supports images only. Could extend to: - - File shares (links to files in Nextcloud) - - Deck cards - - Calendar events - - Custom rich objects - -### Multi-format inbound support -- Currently only Apprise-format payloads are accepted as inbound -- Could add support for: - - Discord webhook format (same as outbound, for symmetry) - - Generic JSON (no wrapper) - - Form-encoded payloads (already partially supported for Apprise) - -### Read markers -- Track which messages have been "seen" by external services -- Could return `last-seen` in the response for convenience - -### Typing indicators -- Forward typing indicators from external services to Talk -- Would require a new endpoint: `POST /typing/{roomToken}` - -### System messages -- Currently the voice message MIME type fix is disabled to avoid creating non-voice system messages -- Could enable system messages in the future (e.g., "Bot started", "Room joined") - ---- - -## Security Notes - -- Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API -- The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources -- Debug endpoint is disabled by default and should be re-enabled immediately after troubleshooting diff --git a/TODO.md b/TODO.md index c0076ca..893a582 100644 --- a/TODO.md +++ b/TODO.md @@ -1,40 +1,181 @@ -# nc_bot_webhooks — Pending Tasks - -## Debug Endpoint -- [x] Re-enable admin auth requirement for `/debug` endpoint (WebhookController.php) -- [x] Re-enable admin gate + update docs - -## README / Documentation Fixes -- [x] Section 4: App password path — confirmed "Personal Settings → Security" is correct -- [x] Section 4 (FIXED): Template hint `templates/adminSettings.php:11` — confirmed correct path -- [x] Section 4: l10n string `lib/Settings/Admin.php:43` — confirmed correct path -- [x] Section 5: "Administration → Additional settings" → confirmed correct for current NC -- [x] Section 3: "Settings → Users" → confirmed correct for current NC -- [x] Security table: `allow_local_address: true` description — clarified wording -- [x] Image cleanup cron grep — already correct in README (`grep 'nc_bot_webhooks: purged'`) -- [x] Troubleshooting: `ncdiscordhook` app ID — not in README (only in agent.md, which is correct) -- [x] Payload Mapping: `mapPayload` title — confirmed bold formatting correct -- [x] Payload Mapping: `type` icon mapping — confirmed actual emojis listed -- [x] Home Assistant YAML: `message` vs `body` — `message` works via fallback, no change needed -- [x] Version: bumped to 1.2.1 (info.xml + agent.md updated) -- [x] NC max-version: docs say 33, info.xml says 35 — agent.md updated - -## Code Documentation Fixes -- [x] `buildRichObject` docs describe public link shares; code uses TYPE_ROOM shares -- [x] `postToRoom` docs describe JSON body posting; code uses query params -- [x] `prependDisplayName` signature — already documents all 3 params including `$typeIcon` -- [x] `getRooms()` response schema missing `type`, `type_label`, `object_type` -- [x] `saveConfig` docs missing `disabled_rooms` field -- [x] `ensureBotParticipants` visibility — already documented as `private` with call context -- [x] `getBaseUrl()` resolution incompletely documented -- [x] TalkService constructor params: docs say 8, actual 17 -- [x] TalkService.php size: docs say 1138 lines, actual 1904 -- [x] All line numbers in agent.md are stale -- [x] File sizes table: multiple files have wrong line counts - -## Code Issues -- [x] All line numbers in agent.md are stale +# nc_bot_webhooks — Todo ## Future Ideas + - Room type filter `type IN (1,2,3)` misses type 6 (and future types) — consider `type >= 1` or similar - Debug auto-disable timer (2-hour TTL) to prevent accidental prolonged exposure + +## Inbound Messages (apprise-request) + +**Status:** Planning phase + +### Concept + +Add a new endpoint that polls Nextcloud Talk for new messages, returning them in an Apprise-compatible JSON format so external services can read from the chat and trigger actions. + +``` +POST /apps/nc_bot_webhooks/apprise-request//notify/ +``` + +### Request + +```json +{ + "last-seen": 42 +} +``` + +### Response + +```json +{ + "43": { + "version": 0, + "subject": "Alice", + "title": "Message from Alice", + "body": "Hello from the other side!", + "type": "info" + }, + "44": { + "version": 0, + "subject": "Bob", + "title": "Message from Bob", + "body": "Welcome back!", + "type": "info" + } +} +``` + +Each key is the Talk message ID. The keys are always in ascending order by ID. The client finds the largest key for the next request: `Math.max(...Object.keys(response))`. + +> **Note:** JSON objects are technically unordered per spec, but all major implementations preserve insertion order. If strict ordering is required by a client, it can use `Object.keys(response).sort((a, b) => Number(a))` to guarantee sort. + +### Design Decision: Message ID vs Timestamp + +Use **message ID** (integer), not a timestamp. + +Nextcloud Talk's Chat API (`GET ocs/v2.php/apps/spreed/api/v1/chat/{roomToken}/messages`) supports a `since` query parameter that takes a message ID. Message IDs are auto-incrementing integers, making them: + +- **Monotonically ordered** — no clock skew issues between Talk server and webhook caller +- **Precise** — exact message boundary, no ambiguity about which messages fall between two timestamps +- **Efficient** — Talk's DB query can use the index directly; timestamps would require a range scan +- **Consistent** — matches the existing Talk API pattern (the same `since` parameter is used for the standard polling endpoint) + +### Implementation sketch + +1. **New route** in `appinfo/routes.php`: + - `POST /apprise-request/{roomToken}/notify/{token}` + +2. **New WebhookController method** (`receiveAppriseRequest`): + - Validate auth token + - Parse `last-seen` from request body (default: 0 for first poll) + - Call `TalkService::getNewMessages($roomToken, $lastSeen)` + +3. **New TalkService method** (`getNewMessages`): + - Query Talk Chat API with `since={lastSeen}` parameter + - Parse response and map each message to an Apprise-compatible object + - Return array of Apprise-format notification objects + +4. **Admin settings UI**: + - Add a "Triggers" section for configuring inbound triggers + - Per-trigger: room selection, trigger token, filter rules (optional) + +5. **Config storage** (`appconfig`): + - New key `triggers`: JSON object mapping trigger tokens to configuration + +### Open questions + +- Should the endpoint accept an Apprise-format request (like existing webhooks) with a `last-seen` field? Or a custom format? +- Should triggers support message filtering (e.g., only messages matching a regex, only @mentions)? +- Should triggers support one-way dispatch (call an external webhook when a message matches)? +- Rate limiting: how often should external services poll? Should we add a `X-Poll-After` header? + +--- + +## Known Limitations + +### Auth token generation is client-side +- **File:** `js/settings.js:245` +- **Issue:** Auth tokens use `btoa(Math.random() + Date.now())` — not cryptographically secure +- **Fix:** Switch to server-side `random_bytes(32)` generation +- **Priority:** Low (webhook URLs are not publicly discoverable; the threat model doesn't require it) + +### Voice message MIME type fix is disabled +- **File:** `lib/Service/TalkService.php:1556` +- **Issue:** `fixMimeTypeOfVoiceMessage()` is commented out because it creates non-voice shares for voice messages +- **Fix:** Re-enable when upgrading to a Talk version that properly filters voice message shares + +### No rate limiting on webhook endpoints +- **Issue:** Webhook endpoints have no rate limiting — vulnerable to abuse if URLs are exposed +- **Fix:** Add rate limiting via Nextcloud's `IRateLimitManager` or place behind reverse proxy + +### Bot avatar not supported +- **Issue:** NCTalk doesn't support per-message avatars; the `avatar_url` field in Discord payloads is ignored +- **Workaround:** Sender name is prepended to message text as a bold line + +### No per-message avatar support in Talk +- **Impact:** Visual identity of the sending service is conveyed via type icons + sender name, not avatars + +### Bot user requires admin privileges +- **Issue:** The `talk-bot` user must be granted admin access to list all Talk rooms +- **Impact:** Security concern — bot has more privileges than strictly necessary + +### Storage quota impact +- **Issue:** Images count toward the bot user's storage quota +- **Impact:** Large deployments with many image webhooks could exhaust the bot's quota + +### No CSRF protection on webhook endpoints +- **By design:** Webhook endpoints are marked `#[PublicPage]` + `#[NoCSRFRequired]` — auth token is the sole access control + +--- + +## Potential Future Features + +### Message filtering / pattern matching +- Allow triggers to filter incoming messages by regex, keywords, or @mention +- Enables "only notify on specific messages" rather than polling everything + +### Outbound webhook dispatch (trigger → external service) +- When a message matches a trigger, POST to an external webhook URL +- Would turn nc_bot_webhooks into a bidirectional bridge (not just a polling endpoint) + +### Slash command support +- Recognize and execute slash commands (e.g., `/status`, `/ping`) sent to the bot +- Would require the inbound message system first + +### Thread / conversation support +- Post messages within a specific thread (requires Talk thread API integration) + +### Rich object support expansion +- Currently supports images only. Could extend to: + - File shares (links to files in Nextcloud) + - Deck cards + - Calendar events + - Custom rich objects + +### Multi-format inbound support +- Currently only Apprise-format payloads are accepted as inbound +- Could add support for: + - Discord webhook format (same as outbound, for symmetry) + - Generic JSON (no wrapper) + - Form-encoded payloads (already partially supported for Apprise) + +### Read markers +- Track which messages have been "seen" by external services +- Could return `last-seen` in the response for convenience + +### Typing indicators +- Forward typing indicators from external services to Talk +- Would require a new endpoint: `POST /typing/{roomToken}` + +### System messages +- Currently the voice message MIME type fix is disabled to avoid creating non-voice system messages +- Could enable system messages in the future (e.g., "Bot started", "Room joined") + +--- + +## Security Notes + +- Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API +- The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources +- Debug endpoint is disabled by default and should be re-enabled immediately after troubleshooting