- Restructure TODO.md to include planning phase for inbound messages - Add Known Limitations section covering auth token generation, voice MIME type fixes, rate limiting, bot avatars, admin privileges, storage quota, and CSRF - Add Potential Future Features section detailing message filtering, outbound dispatch, slash commands, thread support, rich objects, multi-format inbound, read markers, typing indicators, and system messages - Add Security Notes section - Remove deprecated Future to-do.md file
7.1 KiB
nc_bot_webhooks — Todo
Future Ideas
- Room type filter
type IN (1,2,3)misses type 6 (and future types) — considertype >= 1or 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/<room-token>/notify/<auth-token>
Request
{
"last-seen": 42
}
Response
{
"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
sinceparameter is used for the standard polling endpoint)
Implementation sketch
-
New route in
appinfo/routes.php:POST /apprise-request/{roomToken}/notify/{token}
-
New WebhookController method (
receiveAppriseRequest):- Validate auth token
- Parse
last-seenfrom request body (default: 0 for first poll) - Call
TalkService::getNewMessages($roomToken, $lastSeen)
-
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
- Query Talk Chat API with
-
Admin settings UI:
- Add a "Triggers" section for configuring inbound triggers
- Per-trigger: room selection, trigger token, filter rules (optional)
-
Config storage (
appconfig):- New key
triggers: JSON object mapping trigger tokens to configuration
- New key
Open questions
- Should the endpoint accept an Apprise-format request (like existing webhooks) with a
last-seenfield? 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-Afterheader?
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
IRateLimitManageror place behind reverse proxy
Bot avatar not supported
- Issue: NCTalk doesn't support per-message avatars; the
avatar_urlfield 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-botuser 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-seenin 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