Skip to content

[webhooks] add mms:downloaded webhook event#329

Merged
capcom6 merged 3 commits intomasterfrom
feature/mms-downloaded-webhook
Mar 19, 2026
Merged

[webhooks] add mms:downloaded webhook event#329
capcom6 merged 3 commits intomasterfrom
feature/mms-downloaded-webhook

Conversation

@capcom6
Copy link
Copy Markdown
Owner

@capcom6 capcom6 commented Mar 12, 2026

Summary by CodeRabbit

  • New Features
    • Detect fully downloaded MMS and emit a new webhook event "mms:downloaded".
    • Webhook payloads now include full MMS details: subject, body, received timestamp, sender/recipient and SIM.
    • MMS attachments delivered with metadata (part id, content type, name, size) and Base64-encoded data.
    • Webhook payload union expanded to include the new MMS downloaded payload type.
  • Documentation
    • Base payload wording updated to surface as MessageEventPayload.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

🤖 Pull request artifacts

file commit
app-release.apk 508ff1c
app-release.aab 508ff1c
app-insecure.apk 508ff1c
app-insecure.aab 508ff1c

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Walkthrough

Adds end-to-end MMS downloaded handling: content observer and reader, persisted last-processed MMS ID, new full-MMS model and headers type, ReceiverService wiring to emit a new mms:downloaded webhook with payload, API schema updates, and DI bindings.

Changes

Cohort / File(s) Summary
API Schema
app/src/main/assets/api/swagger.json
Adds MmsDownloadedPayload and MmsDownloadedAttachment schemas; renames SmsEventPayload title → MessageEventPayload; adds "mms:downloaded" to WebhookEvent and includes the new payload in webhook oneOf.
MMS Observer & Reader
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt, app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt
New ContentObserver that tracks new MMS using a high-water mark and queries provider; reader materializes MMS metadata, body, parts and Base64 attachment data with defensive IO and charset handling.
Receiver wiring
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt, app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
Starts/stops MMS observer; ReceiverService now emits WebHookEvent.MmsDownloaded for full MMS messages and maps attachments; elsewhere constructs/consumes MmsHeaders for metadata.
Inbox message model
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
Splits MMS types: introduces MmsHeaders (metadata) and new MMS (full message) with nested Attachment; updates equals/hashCode semantics accordingly.
State persistence
app/src/main/java/me/capcom/smsgateway/modules/receiver/StateStorage.kt
Adds StateStorage wrapper exposing mmsLastProcessedID backed by KeyValueStorage for high-water mark persistence.
Webhook payload & enum
app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt, app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
Adds MmsDownloadedPayload data class (with nested Attachment) and new enum member MmsDownloaded annotated as "mms:downloaded".
DI & module changes
app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt
Registers StateStorage singleton (PreferencesStorage("receiver")) and changes TemporaryStorage binding scope to single.
Misc wiring / logging
app/src/main/java/.../receiver/*.kt
Minor wiring and logging updates to integrate observer lifecycle and error logging via existing services.

Sequence Diagram(s)

sequenceDiagram
    participant RS as ReceiverService
    participant SS as StateStorage
    participant Obs as MmsContentObserver
    participant CP as AndroidContentProvider
    participant Reader as MmsContentReader
    participant WH as WebHooksService

    RS->>Obs: start()
    Obs->>SS: read mmsLastProcessedID
    Obs->>CP: register observer, query max(_id)
    CP-->>Obs: currentMaxId
    Obs->>CP: query new MMS (id > lastId, m_type=132, msg_box=1)
    CP-->>Obs: [mmsId...]
    Obs->>RS: notify(mmsId)
    RS->>Reader: read(context, mmsId)
    Reader->>CP: query metadata, addr, parts, part data
    CP-->>Reader: metadata, sender, parts, data
    Reader-->>RS: MmsMessage (body, subject, attachments, date, subscriptionId)
    RS->>WH: emit(WebHookEvent.MmsDownloaded, MmsDownloadedPayload)
    WH-->>RS: ack
    RS->>SS: set mmsLastProcessedID(newLastId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ready

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[webhooks] add mms:downloaded webhook event' directly aligns with the main objective of the pull request, which introduces support for a new MMS downloaded webhook event.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/mms-downloaded-webhook
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt`:
- Around line 10-12: The observer currently advances the high-water mark
unconditionally after invoking onMmsDownloaded, which allows failed/transient
reads to be skipped forever; change the onMmsDownloaded callback signature in
MmsContentObserver from (mmsId: Long) -> Unit to (mmsId: Long) -> Boolean, call
it and only update newMark when it returns true, leaving the mark unchanged on
false so the MMS ID remains retryable; update all callers (notably
ReceiverService.processMmsDownloaded()) to return false for soft/transient
failures and true on success.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt`:
- Around line 82-91: The loop that reads MMS parts (partCursor) only uses the
text column for contentType "text/plain" and ignores file-backed parts; add a
helper function readTextPart(context, partId) that opens
content://mms/part/{partId} via contentResolver.openInputStream, reads and
decodes the stream to a String (respecting charset if available), and returns
the text; in the part-reading loop (where contentType == "text/plain") call
readTextPart when c.getString(2) is null or blank and add the result to
bodyParts if non-blank, making sure to catch/handle IO exceptions and always
close streams.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt`:
- Around line 153-155: The payload construction in ReceiverService uses the
provider row ID (mmsId) for MmsDownloadedPayload.messageId, which breaks
correlation with process() that emits mms:received using message.messageId ?:
message.transactionId; update the code that builds MmsDownloadedPayload so it
uses the same identifier logic (message.messageId ?: message.transactionId)
instead of mmsId, and if those header IDs are not available from
MmsContentReader.read()/MmsContentReader.MmsMessage, modify
MmsContentReader.read() and its MmsMessage type to surface header identifiers
(messageId and transactionId) so the downloader can set the same identifier in
MmsDownloadedPayload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64dbe45f-ac82-4d77-a72e-7c2ccf444621

📥 Commits

Reviewing files that changed from the base of the PR and between e549ecd and f3e9be3.

📒 Files selected for processing (6)
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt

Comment thread app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt Outdated
Comment thread app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt Outdated
@capcom6 capcom6 force-pushed the feature/mms-downloaded-webhook branch from f3e9be3 to 3682988 Compare March 13, 2026 05:08
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)

111-128: ⚠️ Potential issue | 🟠 Major

Keep messageId consistent between mms:received and mms:downloaded events.

The mms:received event (line 100) uses message.messageId ?: message.transactionId, but mms:downloaded uses the provider row ID (passed from MmsContentObserver.processMmsDownloaded as mmsId.toString()). Webhook consumers cannot reliably correlate these two stages of the same MMS if the identifiers differ.

Consider surfacing the original MMS header identifiers (messageId, transactionId) from MmsContentReader so the downloaded event can use consistent identification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt`
around lines 111 - 128, The mms:downloaded event is currently using the provider
row ID instead of the same identifier used by mms:received, so update the MMS
pipeline to surface original MMS header IDs from MmsContentReader (messageId and
transactionId) through MmsContentObserver.processMmsDownloaded into
ReceiverService and use the same selection logic (message.messageId ?:
message.transactionId) when constructing the WebHookEvent.MmsDownloaded /
MmsDownloadedPayload (the messageId field) so consumers can correlate
mms:received and mms:downloaded reliably.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt (1)

82-97: ⚠️ Potential issue | 🟠 Major

Don't advance the high-water mark past a failed MMS.

Line 95 updates storage.mmsLastProcessedID = mmsId unconditionally after processing, even when an exception occurs. A transient failure (e.g., provider read error) will permanently skip that MMS since the mark advances past it.

Consider only advancing the mark on successful processing, or changing the callback pattern to allow soft failures.

Suggested fix
         cursor.use { c ->
             while (c.moveToNext()) {
                 val mmsId = c.getLong(0)
                 try {
                     processMmsDownloaded(mmsId)
+                    storage.mmsLastProcessedID = mmsId
                 } catch (e: Exception) {
                     logsService.insert(
                         LogEntry.Priority.ERROR,
                         MODULE_NAME,
                         "Failed processing downloaded MMS (id=$mmsId)",
                         mapOf("mmsId" to mmsId)
                     )
+                    break  // Stop processing; retry this MMS on next onChange
                 }
-                storage.mmsLastProcessedID = mmsId
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt`
around lines 82 - 97, The loop in MmsContentObserver currently advances the
high-water mark unconditionally (storage.mmsLastProcessedID = mmsId) even when
processMmsDownloaded(mmsId) throws; change it so the mark is updated only after
successful processing — e.g., move the storage.mmsLastProcessedID assignment
into the try block after processMmsDownloaded returns (or set it only when no
exception is caught), and keep the logsService.insert call in the catch so
failures do not advance the mark and the MMS can be retried.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt (1)

87-91: ⚠️ Potential issue | 🟡 Minor

Handle text/plain parts with file-backed storage.

When a content://mms/part row has ct='text/plain' and _data is non-null, the text should be read from the part URI stream rather than the text column. The current code only checks the text column, causing the body to be empty when text is stored file-backed (atypical but observed in some OEM implementations).

Suggested fix
                 if (contentType == "text/plain") {
-                    val text = c.getString(2)
+                    val text = c.getString(2)?.takeIf { it.isNotBlank() }
+                        ?: readTextFromPart(resolver, partId)
                     if (!text.isNullOrBlank()) {
                         bodyParts.add(text)
                     }

Add a helper function:

private fun readTextFromPart(resolver: ContentResolver, partId: Long): String? {
    return try {
        resolver.openInputStream(Uri.parse("content://mms/part/$partId"))?.use { input ->
            input.bufferedReader().readText().takeIf { it.isNotBlank() }
        }
    } catch (_: Exception) {
        null
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt`
around lines 87 - 91, The text/plain branch in MmsContentReader.kt currently
only reads c.getString(2) and skips file-backed parts; add a helper function
readTextFromPart(resolver: ContentResolver, partId: Long): String? (as
suggested) that opens content://mms/part/{partId} and returns buffered text or
null, then in the contentType == "text/plain" handling check the _data/part id
from the cursor (use the part id column) and if the text from c.getString(2) is
null/blank but _data is present, call readTextFromPart(resolver, partId) and add
its result to bodyParts if non-blank so file-backed text parts are included.
🧹 Nitpick comments (1)
app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt (1)

63-67: Consider using single {} instead of factory {} for StateStorage.

The factory {} binding creates a new StateStorage instance on each injection. While this works correctly (all instances share the same underlying SharedPreferences), using single {} would be more efficient and consistent with the intent of having a single state manager.

♻️ Suggested change
-    factory {
+    single {
         StateStorage(
             PreferencesStorage(get(), "receiver")
         )
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt` around
lines 63 - 67, Replace the Koin binding for StateStorage which currently uses
factory { StateStorage(PreferencesStorage(get(), "receiver")) } with a singleton
binding: change the provider to single { StateStorage(PreferencesStorage(get(),
"receiver")) } so the StateStorage is created once and reused; update the
binding where factory is used to refer to StateStorage and ensure no other code
relies on multiple instances.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt`:
- Around line 33-36: The code creates two HandlerThread instances (local thread
and field handlerThread) but only stores/stops handlerThread while the
ContentObserver uses the local thread, leaking the observer thread; fix by
creating a single HandlerThread and assigning it to the field (handlerThread =
HandlerThread("MmsContentObserver").apply { start() }) and then construct the
ContentObserver with Handler(handlerThread.looper) so the same thread is used
for observation and can be properly quit/stopped later (ensure any stop()/quit()
calls reference the same handlerThread field).

---

Duplicate comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt`:
- Around line 82-97: The loop in MmsContentObserver currently advances the
high-water mark unconditionally (storage.mmsLastProcessedID = mmsId) even when
processMmsDownloaded(mmsId) throws; change it so the mark is updated only after
successful processing — e.g., move the storage.mmsLastProcessedID assignment
into the try block after processMmsDownloaded returns (or set it only when no
exception is caught), and keep the logsService.insert call in the catch so
failures do not advance the mark and the MMS can be retried.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt`:
- Around line 87-91: The text/plain branch in MmsContentReader.kt currently only
reads c.getString(2) and skips file-backed parts; add a helper function
readTextFromPart(resolver: ContentResolver, partId: Long): String? (as
suggested) that opens content://mms/part/{partId} and returns buffered text or
null, then in the contentType == "text/plain" handling check the _data/part id
from the cursor (use the part id column) and if the text from c.getString(2) is
null/blank but _data is present, call readTextFromPart(resolver, partId) and add
its result to bodyParts if non-blank so file-backed text parts are included.

In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt`:
- Around line 111-128: The mms:downloaded event is currently using the provider
row ID instead of the same identifier used by mms:received, so update the MMS
pipeline to surface original MMS header IDs from MmsContentReader (messageId and
transactionId) through MmsContentObserver.processMmsDownloaded into
ReceiverService and use the same selection logic (message.messageId ?:
message.transactionId) when constructing the WebHookEvent.MmsDownloaded /
MmsDownloadedPayload (the messageId field) so consumers can correlate
mms:received and mms:downloaded reliably.

---

Nitpick comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt`:
- Around line 63-67: Replace the Koin binding for StateStorage which currently
uses factory { StateStorage(PreferencesStorage(get(), "receiver")) } with a
singleton binding: change the provider to single {
StateStorage(PreferencesStorage(get(), "receiver")) } so the StateStorage is
created once and reused; update the binding where factory is used to refer to
StateStorage and ensure no other code relies on multiple instances.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a49310a7-a26b-4d06-948c-e54ddd1eab4f

📥 Commits

Reviewing files that changed from the base of the PR and between f3e9be3 and 3682988.

📒 Files selected for processing (10)
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/StateStorage.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
  • app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt`:
- Around line 87-99: The code compares contentType using exact string matches so
MIME parameters/casing break the text-vs-attachment branching; normalize the
header before branching by deriving a normalized type from contentType (e.g.,
val normalized = contentType.substringBefore(';').trim().lowercase()) and use
that normalized value for the conditional checks (replace checks against
contentType with normalized == "text/plain" and normalized !=
"application/smil"), while continuing to pass the original contentType to
readTextPart/readers if they need parameters; update the comparisons around the
contentType variable in MmsContentReader.kt (the block that uses partId,
readTextPart, and bodyParts) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c4b7e54-4e9a-4b34-88dd-eb105a80cec3

📥 Commits

Reviewing files that changed from the base of the PR and between 3682988 and fe375aa.

📒 Files selected for processing (3)
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt
  • app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentObserver.kt

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/src/main/assets/api/swagger.json (2)

4-32: Missing required array for non-nullable properties.

Comparing with the Kotlin Attachment class, partId, contentType, and size are non-nullable, so they should be marked as required in the schema. This ensures API consumers relying on the OpenAPI spec for validation or code generation get accurate type information.

📝 Proposed fix to add required fields
           "size": {
             "description": "Size in bytes, 0 if unknown.",
             "type": "integer"
           }
         },
+        "required": [
+          "partId",
+          "contentType",
+          "size"
+        ],
         "type": "object"
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 4 - 32, The
MmsDownloadedAttachment schema is missing a required array for non-nullable
properties; update the "MmsDownloadedAttachment" object to include "required":
["partId","contentType","size"] so that partId, contentType, and size are
declared required (matching the Kotlin Attachment class) and ensure the types
remain unchanged under the properties block.

33-68: Missing required array and consider adding maxItems constraint.

  1. Based on the Kotlin MmsDownloadedPayload class, attachments and receivedAt are non-nullable and should be marked as required in the nested object definition.

  2. The static analysis tool flags the attachments array lacking a maxItems constraint. While MMS messages are carrier-size-limited, adding a reasonable upper bound (e.g., 100) improves API predictability for consumers.

📝 Proposed fix to add required fields and maxItems
               "attachments": {
                 "description": "Metadata for non-text MMS parts, including optional Base64 content.",
                 "items": {
                   "$ref": "#/components/schemas/MmsDownloadedAttachment"
                 },
+                "maxItems": 100,
                 "type": "array"
               },
               "body": {
                 "description": "Aggregated text content of the MMS message.",
                 "nullable": true,
                 "type": "string"
               },
               "receivedAt": {
                 "description": "The timestamp when the MMS message was received.",
                 "format": "date-time",
                 "type": "string"
               },
               "subject": {
                 "description": "Message subject line.",
                 "nullable": true,
                 "type": "string"
               }
             },
+            "required": [
+              "attachments",
+              "receivedAt"
+            ],
             "type": "object"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 33 - 68, The nested object
schema for MmsDownloadedPayload is missing a required array and attachments
lacks a maxItems constraint: update the MmsDownloadedPayload schema (the inner
object under "allOf") to include a "required" array listing "attachments" and
"receivedAt", and add a sensible "maxItems" (e.g., 100) to the "attachments"
array definition to bound its length and match the Kotlin non-nullability of
attachments and receivedAt.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/main/assets/api/swagger.json`:
- Around line 4-32: The MmsDownloadedAttachment schema is missing a required
array for non-nullable properties; update the "MmsDownloadedAttachment" object
to include "required": ["partId","contentType","size"] so that partId,
contentType, and size are declared required (matching the Kotlin Attachment
class) and ensure the types remain unchanged under the properties block.
- Around line 33-68: The nested object schema for MmsDownloadedPayload is
missing a required array and attachments lacks a maxItems constraint: update the
MmsDownloadedPayload schema (the inner object under "allOf") to include a
"required" array listing "attachments" and "receivedAt", and add a sensible
"maxItems" (e.g., 100) to the "attachments" array definition to bound its length
and match the Kotlin non-nullability of attachments and receivedAt.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68fbb8fe-b29a-4a3c-bf34-6f172b7be64a

📥 Commits

Reviewing files that changed from the base of the PR and between fe375aa and 28c754d.

📒 Files selected for processing (1)
  • app/src/main/assets/api/swagger.json

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/src/main/assets/api/swagger.json (2)

158-185: Consider aligning schema name with the new title.

The schema name remains SmsEventPayload while the title was updated to MessageEventPayload to reflect broader usage beyond SMS. This inconsistency could cause confusion for API consumers. Consider renaming the schema definition to MessageEventPayload for consistency, though this would be a breaking change for existing clients.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 158 - 185, The schema key
is still named SmsEventPayload while its title was changed to
MessageEventPayload; rename the schema definition from SmsEventPayload to
MessageEventPayload and update all references to
components.schemas.SmsEventPayload (e.g., $ref values) across the OpenAPI spec
so they point to components.schemas.MessageEventPayload, keeping the "title":
"MessageEventPayload" and existing properties unchanged; if you need to avoid
breaking clients instead add an aliased schema (keep SmsEventPayload as a $ref
to the new MessageEventPayload) and document the deprecation.

45-50: Consider adding maxItems constraint for the attachments array.

Static analysis flagged that the attachments array lacks a maxItems constraint. While MMS messages are carrier-size-limited (typically 1-3 MB total), adding a reasonable upper bound (e.g., "maxItems": 100) provides input validation at the API layer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 45 - 50, The attachments
array schema (property "attachments" referencing
"#/components/schemas/MmsDownloadedAttachment") needs an explicit upper bound to
validate input; update the Swagger/OpenAPI fragment for "attachments" to include
a "maxItems" (e.g., 100) so the array cannot exceed the reasonable size limit
for MMS payloads and keep the existing "type": "array" and "$ref" intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/main/assets/api/swagger.json`:
- Around line 158-185: The schema key is still named SmsEventPayload while its
title was changed to MessageEventPayload; rename the schema definition from
SmsEventPayload to MessageEventPayload and update all references to
components.schemas.SmsEventPayload (e.g., $ref values) across the OpenAPI spec
so they point to components.schemas.MessageEventPayload, keeping the "title":
"MessageEventPayload" and existing properties unchanged; if you need to avoid
breaking clients instead add an aliased schema (keep SmsEventPayload as a $ref
to the new MessageEventPayload) and document the deprecation.
- Around line 45-50: The attachments array schema (property "attachments"
referencing "#/components/schemas/MmsDownloadedAttachment") needs an explicit
upper bound to validate input; update the Swagger/OpenAPI fragment for
"attachments" to include a "maxItems" (e.g., 100) so the array cannot exceed the
reasonable size limit for MMS payloads and keep the existing "type": "array" and
"$ref" intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da1c91fe-62f8-41fe-a7a3-85bfbfa257bc

📥 Commits

Reviewing files that changed from the base of the PR and between 28c754d and cd4f0b8.

📒 Files selected for processing (4)
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsContentReader.kt
  • app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsDownloadedPayload.kt

jacobsparts and others added 2 commits March 14, 2026 16:55
This adds a second-stage MMS event that fires after an MMS has been fully downloaded by the system, providing the actual message content (body, subject, attachments). The existing mms:received event only fires on initial notification — before the content is available.

Three main components:

1. MmsContentObserver — Watches for new MMS messages via a ContentObserver on
content://mms.

- Runs on a dedicated HandlerThread to avoid blocking the main thread.
- Tracks a high-water mark (the last processed MMS _id) in SharedPreferences so it never re-processes old messages across restarts.
- On initialization, seeds the high-water mark to the current max ID (so it only fires for messages arriving after the observer starts).
- When onChange fires, it queries for new rows where m_type = 132 (retrieve-conf, meaning fully downloaded) and msg_box = 1 (inbox), then invokes the callback for each.

2. MmsContentReader — A stateless utility that reads MMS content from the content provider given an MMS ID. It extracts:

- Message-level fields: subject, date (converting from seconds to millis), and sub_id (subscription/SIM, API 22+).
- Sender address: queries the addr sub-table with type = 137 (FROM).
- Parts: iterates content://mms/<id>/part — aggregates text/plain parts into a body string, skips SMIL parts, and collects metadata (MIME type, filename, file size) for all other parts as attachments. No binary data is included in the payload.

3. Integration into ReceiverService:

- Creates and starts the MmsContentObserver alongside the existing broadcast receivers.
- The callback (processMmsDownloaded) reads the MMS via MmsContentReader, resolves the SIM slot/phone number from the subscription ID, builds an MmsDownloadedPayload, and emits it as a WebHookEvent.MmsDownloaded webhook event.

4. Supporting changes:

- WebHookEvent enum gets a new MmsDownloaded("mms:downloaded") variant.
- MmsDownloadedPayload extends MessageEventPayload with body, subject, attachments list, and receivedAt timestamp.
- swagger.json documents the new event, payload schema, and attachment schema.

Data flow:

System downloads MMS →
 content://mms changes →
 ContentObserver fires →
 query for new retrieve-conf inbox rows →
 MmsContentReader parses content →
 MmsDownloadedPayload built →
 webhook emitted to subscribers
@capcom6 capcom6 force-pushed the feature/mms-downloaded-webhook branch from cd4f0b8 to ec5d738 Compare March 14, 2026 09:55
@capcom6
Copy link
Copy Markdown
Owner Author

capcom6 commented Mar 15, 2026

@jacobsparts, hi!

Could you test the APK from this PR?

Thanks!

@capcom6 capcom6 added the ready label Mar 18, 2026
@jacobsparts
Copy link
Copy Markdown
Contributor

@capcom6 Hello! It is working well for my use case. Thanks and let me know if you need anything else.

@capcom6
Copy link
Copy Markdown
Owner Author

capcom6 commented Mar 18, 2026

@jacobsparts , thank you for your feedback!

@capcom6 capcom6 merged commit c1682a7 into master Mar 19, 2026
3 checks passed
@capcom6 capcom6 deleted the feature/mms-downloaded-webhook branch March 19, 2026 00:50
@thomasbonsirven
Copy link
Copy Markdown

Thank you all

Thank you all for your work on this, it is highly appreciated.
I have been following this thread from the beginning and it is of great interest for our company.
I will test it as soon as I have some time available in my schedule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants