From 2f6162922d06088d47180d5ccc13737da7852834 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:10:19 -0400 Subject: [PATCH] Add support for clean messages (#226) # Description This PR adds support for the `/messages/clean` endpoint. # License I confirm that this contribution is made under the terms of the MIT license and that I have the authority necessary to make this contribution on behalf of its copyright owner. --------- Co-authored-by: Blag --- .../com/nylas/models/CleanMessageRequest.kt | 102 ++++++++++++++ .../com/nylas/models/CleanMessageResponse.kt | 129 ++++++++++++++++++ .../kotlin/com/nylas/resources/Messages.kt | 15 ++ .../com/nylas/resources/MessagesTests.kt | 48 +++++++ 4 files changed, 294 insertions(+) create mode 100644 src/main/kotlin/com/nylas/models/CleanMessageRequest.kt create mode 100644 src/main/kotlin/com/nylas/models/CleanMessageResponse.kt diff --git a/src/main/kotlin/com/nylas/models/CleanMessageRequest.kt b/src/main/kotlin/com/nylas/models/CleanMessageRequest.kt new file mode 100644 index 00000000..e93af0f4 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/CleanMessageRequest.kt @@ -0,0 +1,102 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +/** + * Class representation of a Nylas clean message request + */ +data class CleanMessageRequest( + /** + * IDs of the email messages to clean. + */ + @Json(name = "message_id") + val messageId: List, + /** + * If true, removes link-related tags () from the email message while keeping the text. + */ + @Json(name = "ignore_links") + val ignoreLinks: Boolean? = null, + /** + * If true, removes images from the email message. + */ + @Json(name = "ignore_images") + val ignoreImages: Boolean? = null, + /** + * If true, converts images in the email message to Markdown. + */ + @Json(name = "images_as_markdown") + val imagesAsMarkdown: Boolean? = null, + /** + * If true, removes table-related tags (, ) from the email message while keeping rows. + */ + @Json(name = "ignore_tables") + val ignoreTables: Boolean? = null, + /** + * If true, removes phrases such as "Best" and "Regards" in the email message signature. + */ + @Json(name = "remove_conclusion_phrases") + val removeConclusionPhrases: Boolean? = null, +) { + + /** + * Builder for the [CleanMessageRequest] class. + * @param messageId IDs of the email messages to clean. + */ + data class Builder( + private val messageId: List, + ) { + private var ignoreLinks: Boolean? = null + private var ignoreImages: Boolean? = null + private var imagesAsMarkdown: Boolean? = null + private var ignoreTables: Boolean? = null + private var removeConclusionPhrases: Boolean? = null + + /** + * If true, removes link-related tags () from the email message while keeping the text. + * @param ignoreLinks The boolean value to set. + * @return The [Builder] instance. + */ + fun ignoreLinks(ignoreLinks: Boolean) = apply { this.ignoreLinks = ignoreLinks } + + /** + * If true, removes images from the email message. + * @param ignoreImages The boolean value to set. + * @return The [Builder] instance. + */ + fun ignoreImages(ignoreImages: Boolean) = apply { this.ignoreImages = ignoreImages } + + /** + * If true, converts images in the email message to Markdown. + * @param imagesAsMarkdown The boolean value to set. + * @return The [Builder] instance. + */ + fun imagesAsMarkdown(imagesAsMarkdown: Boolean) = apply { this.imagesAsMarkdown = imagesAsMarkdown } + + /** + * If true, removes table-related tags (
, ,
, ) from the email message while keeping rows. + * @param ignoreTables The boolean value to set. + * @return The [Builder] instance. + */ + fun ignoreTables(ignoreTables: Boolean) = apply { this.ignoreTables = ignoreTables } + + /** + * If true, removes phrases such as "Best" and "Regards" in the email message signature. + * @param removeConclusionPhrases The boolean value to set. + * @return The [Builder] instance. + */ + fun removeConclusionPhrases(removeConclusionPhrases: Boolean) = apply { this.removeConclusionPhrases = removeConclusionPhrases } + + /** + * Builds the [CleanMessageRequest] instance. + * @return The [CleanMessageRequest] instance. + */ + fun build() = CleanMessageRequest( + messageId = messageId, + ignoreLinks = ignoreLinks, + ignoreImages = ignoreImages, + imagesAsMarkdown = imagesAsMarkdown, + ignoreTables = ignoreTables, + removeConclusionPhrases = removeConclusionPhrases, + ) + } +} diff --git a/src/main/kotlin/com/nylas/models/CleanMessageResponse.kt b/src/main/kotlin/com/nylas/models/CleanMessageResponse.kt new file mode 100644 index 00000000..07775ed6 --- /dev/null +++ b/src/main/kotlin/com/nylas/models/CleanMessageResponse.kt @@ -0,0 +1,129 @@ +package com.nylas.models + +import com.squareup.moshi.Json + +data class CleanMessageResponse( + /** + * Grant ID of the Nylas account. + */ + @Json(name = "grant_id") + val grantId: String, + /** + * The type of object. + */ + @Json(name = "object") + private val obj: String = "message", + /** + * An array of message senders. + */ + @Json(name = "from") + val from: List? = null, + /** + * The unique identifier for the message. + * Note: The ID may not be present for scheduled messages until the message is sent. + */ + @Json(name = "id") + val id: String? = null, + /** + * An array of bcc recipients. + */ + @Json(name = "bcc") + val bcc: List? = null, + /** + * An array of cc recipients. + */ + @Json(name = "cc") + val cc: List? = null, + /** + * An array of name and email pairs that override the sent reply-to headers. + */ + @Json(name = "reply_to") + val replyTo: List? = null, + /** + * A short snippet of the message body. + * This is the first 100 characters of the message body, with any HTML tags removed. + */ + @Json(name = "snippet") + val snippet: String? = null, + /** + * The message subject. + */ + @Json(name = "subject") + val subject: String? = null, + /** + * A reference to the parent thread object. + * If this is a new draft, the thread will be empty. + */ + @Json(name = "thread_id") + val threadId: String? = null, + /** + * The full HTML message body. + * Messages with only plain-text representations are up-converted to HTML. + */ + @Json(name = "body") + val body: String? = null, + /** + * Whether or not the message has been starred by the user. + */ + @Json(name = "starred") + val starred: Boolean? = null, + /** + * Whether or not the message has been read by the user. + */ + @Json(name = "unread") + val unread: Boolean? = null, + /** + * The ID of the folder(s) the message appears in. + */ + @Json(name = "folders") + val folders: List? = null, + /** + * An array of message recipients. + */ + @Json(name = "to") + val to: List? = null, + /** + * An array of files attached to the message. + */ + @Json(name = "attachments") + val attachments: List? = null, + /** + * The message headers. + * Only present if the 'fields' query parameter is set to includeHeaders. + */ + @Json(name = "headers") + val headers: List? = null, + /** + * Unix timestamp of when the message was created. + */ + @Json(name = "created_at") + val createdAt: Long? = null, + /** + * Unix timestamp of when the message was received by the mail server. + * This may be different from the unverified Date header in raw message object. + */ + @Json(name = "date") + val date: Long? = null, + /** + * A list of key-value pairs storing additional data. + */ + @Json(name = "metadata") + val metadata: Map? = null, + /** + * The ID of the scheduled message. + * Only present if the message was scheduled to be sent. + */ + @Json(name = "schedule_id") + val scheduleId: String? = null, + /** + * The time the message was scheduled to be sent, in epoch time. + * Only present if the message was scheduled to be sent. + */ + @Json(name = "send_at") + val sendAt: Long? = null, + /** + * The cleaned HTML message body. + */ + @Json(name = "conversation") + val conversation: String? = null, +) diff --git a/src/main/kotlin/com/nylas/resources/Messages.kt b/src/main/kotlin/com/nylas/resources/Messages.kt index ccaed314..b85200f7 100644 --- a/src/main/kotlin/com/nylas/resources/Messages.kt +++ b/src/main/kotlin/com/nylas/resources/Messages.kt @@ -145,4 +145,19 @@ class Messages(client: NylasClient) : Resource(client, Message::class.j val responseType = Types.newParameterizedType(Response::class.java, StopScheduledMessageResponse::class.java) return client.executeDelete(path, responseType, overrides = overrides) } + + /** + * Clean messages + * @param identifier The identifier of the grant to act upon + * @param requestBody The values to clean the message with + * @return The list of cleaned messages + */ + @Throws(NylasApiError::class, NylasSdkTimeoutError::class) + fun cleanConversation(identifier: String, requestBody: CleanMessageRequest): ListResponse { + val path = String.format("v3/grants/%s/messages/clean", identifier) + val adapter = JsonHelper.moshi().adapter(CleanMessageRequest::class.java) + val serializedRequestBody = adapter.toJson(requestBody) + val responseType = Types.newParameterizedType(ListResponse::class.java, CleanMessageResponse::class.java) + return client.executePut(path, responseType, serializedRequestBody) + } } diff --git a/src/test/kotlin/com/nylas/resources/MessagesTests.kt b/src/test/kotlin/com/nylas/resources/MessagesTests.kt index 1250e611..571284b8 100644 --- a/src/test/kotlin/com/nylas/resources/MessagesTests.kt +++ b/src/test/kotlin/com/nylas/resources/MessagesTests.kt @@ -525,6 +525,54 @@ class MessagesTests { } } + @Nested + inner class CleanMessageTests { + private lateinit var grantId: String + private lateinit var mockNylasClient: NylasClient + private lateinit var messages: Messages + + @BeforeEach + fun setup() { + grantId = "abc-123-grant-id" + mockNylasClient = Mockito.mock(NylasClient::class.java) + messages = Messages(mockNylasClient) + } + + @Test + fun `cleaning a message calls requests with the correct params`() { + val messageId = "message-123" + val adapter = JsonHelper.moshi().adapter(CleanMessageRequest::class.java) + val cleanMessageRequest = + CleanMessageRequest.Builder(listOf(messageId)) + .ignoreLinks(true) + .ignoreImages(true) + .imagesAsMarkdown(true) + .ignoreTables(true) + .removeConclusionPhrases(true) + .build() + + messages.cleanConversation(grantId, cleanMessageRequest) + + val pathCaptor = argumentCaptor() + val typeCaptor = argumentCaptor() + val requestBodyCaptor = argumentCaptor() + val queryParamCaptor = argumentCaptor() + val overrideParamCaptor = argumentCaptor() + verify(mockNylasClient).executePut>( + pathCaptor.capture(), + typeCaptor.capture(), + requestBodyCaptor.capture(), + queryParamCaptor.capture(), + overrideParamCaptor.capture(), + ) + + assertEquals("v3/grants/$grantId/messages/clean", pathCaptor.firstValue) + assertEquals(Types.newParameterizedType(ListResponse::class.java, CleanMessageResponse::class.java), typeCaptor.firstValue) + assertEquals(adapter.toJson(cleanMessageRequest), requestBodyCaptor.firstValue) + assertNull(queryParamCaptor.firstValue) + } + } + @Nested inner class ResourceTests { private lateinit var grantId: String
, ,