Skip to content

Creating a message with image upload AND getting its ts #1347

@marusic1514

Description

@marusic1514

I am trying to create a message with an image upload AND also have the ts (format "1723570494.731189", not just Unix ts) of when it happened so that I can use it as reference and update that message later if necessary or post in its thread.

I have tried the following methods and this is how each failed me:

1. Doing the filesGetUploadUrlExternal + postMultipart + filesCompleteUploadExternal

the approach roughly works as follows:

        val slack = Slack()
        val os = ByteArrayOutputStream()
        ImageIO.write(bufferedImage, "png", os)
        val imageByteArray = os.toByteArray()
        val imageBytes: ByteString = imageByteArray.toByteString()
        val byteArr = imageBytes.toRequestBody("image/png".toMediaTypeOrNull())
        val resGetUrl = slack.methods(token).filesGetUploadURLExternal {
            it.filename(fileName)
            it.token(token)
            it.length(imageBytes.size)
        }
        val uploadUrl = resGetUrl.uploadUrl
        val fileId = resGetUrl.fileId

        val res = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("file", fileName, byteArr)
            .addFormDataPart("channels", channelId)
            .build()
        slack.httpClient.postMultipart(uploadUrl, chat.apiToken, res)
        slack.methods(chat.apiToken).filesCompleteUploadExternal {
            it.token(chat.apiToken)
            it.files(listOf(FilesCompleteUploadExternalRequest.FileDetails(fileId, "")))
            // note initial comment is ugly and is not in markdown
            it.initialComment(
                buildString {
                    appendLine("*${notice.header}*")
                    append(notice.textMain)
                }
            )
            it.channelId(channelId)
        }

This is great in terms of actually posting the whole text that I want to post along with the image. The problems with this approach, however are:

  1. The initialComment does not allow me to use blocks (as opposed to post to https://slack.com/api/chat.postMessage), so I cannot get it to format my message the way Id want it to. I am ok with this, though I wish initialComment had more flexibility.
  2. The result does not return the ts of the message via which I could later do an update to that message. This is a huge problem for me -- I want to be able to track the messages that I have posted and be able to modify them / send updates in a thread. I understand that this is due to async nature of the filesCompleteUploadExternal, but I nevertheless need a reference to the message I am posting. Going through message history and trying to later guess the message that was created with completeUploadExternal seems like a bad solution here.

2. filesGetUploadUrlExternal + postMultipart + filesCompleteUploadExternal + permaLink + ImageBlock

This works the same as above, except it does not post the file to a specific channel when it uploads it and it does not write the inital comment. Instead, it obtains the permalink of the file that got uploaded. It then makes a post message with an image block where the image is referenced by permaLink

        val permaLink = slack.methods(chat.apiToken).filesCompleteUploadExternal {
            it.token(chat.apiToken)
            it.files(listOf(FilesCompleteUploadExternalRequest.FileDetails(fileId, "")))
            // note initial comment is ugly
            it.initialComment(
                buildString {
                    appendLine("*${notice.header}*")
                    append(notice.textMain)
                }
            ) // note: this does not have rich text capabilities
            it.channelId(channelId)
        }.let {
            val firstFile = it.files.first()
            firstFile.permalink
        }
        rest.post(
            url = "https://slack.com/api/chat.postMessage",
            json = SlackUtil.Message(
                channelId = channelId,
                text = notice.title,
                blocks = listOfNotNull(
                    SlackUtil.Message.SectionBlock(
                        text = SlackUtil.Message.TextBlock(text = translate("**${notice.header}**"))
                    ),
                    SlackUtil.Message.SectionBlock(text = SlackUtil.Message.TextBlock(translate(notice.textMain))),
                    SlackUtil.Message.ImageBlock(image_url = permaLink, alt_text = "alt text"),
                ),
            ),
            headers = getHeaders(),
        )

The problem is that this fails because this runs immediately after upload, so it just errors out because it cannot obtain the file based on the permalink (as far as I understand at least). The response is the following:

{
  "ok" : false,
  "error" : "invalid_blocks",
  "errors" : [ "invalid slack file [json-pointer:/blocks/2/slack_file]" ],
  "response_metadata" : {
    "messages" : [ "[ERROR] invalid slack file [json-pointer:/blocks/2/slack_file]" ]
  }
}

If I were to wait a split second after uploadExternal is called and before the post with the permaLink happens, then the result is a successful posting of a message with the image (exactly how I'd want it! formatted, with the ts of the message which I can later use to update the message or post in its thread).

The response body of that looks like this:

{
  "ok" : true,
  "channel" : <channelId>,
  "ts" : "1723570494.731189",
  "message" : {
  ....

The problem with this is that I do not know how long to wait to be able to use the upload.

3. Finally, since I can obtain the permaLink but cannot use it in an image block, I tried doing the filesGetUploadUrlExternal + postMultipart + filesCompleteUploadExternal + permaLink + TextBlock

This is roughly the same as the second attempt, except I tried to bypass the image upload not being complete by using the permalink in the section text block instead of image block, hoping that it would unfurl and display the image when the image finally uploads. It does create a link to the image which can be clicked on and the file will be displayed, but it just does not display the image as part of the message as I'd hope it would

this is the code for the attempt:

 val permaLink = slack.methods(chat.apiToken).filesCompleteUploadExternal {
            it.token(chat.apiToken)
            it.files(listOf(FilesCompleteUploadExternalRequest.FileDetails(fileId, "")))
        }.let {
            val firstFile = it.files.first()
            firstFile.permalink
        }

        rest.post(
            url = "https://slack.com/api/chat.postMessage",
            json = SlackUtil.Message(
                channelId = channelId,
                text = notice.title,
                blocks = listOfNotNull(
                    SlackUtil.Message.SectionBlock(
                        text = SlackUtil.Message.TextBlock(text = translate("**${notice.header}**"))
                    ),
                    SlackUtil.Message.SectionBlock(text = SlackUtil.Message.TextBlock(translate(notice.textMain))),
                    SlackUtil.Message.SectionBlock(
                        text = SlackUtil.Message.TextBlock(
                            text = translate("**[my image]($permaLink)**")
                        )
                    )
                ),
            ),
            headers = getHeaders(),
        )

So it just has "my image" link at the bottom of the message that you can click on and it would display the image, which is not quite what I'd want.

The Slack SDK version

api("com.slack.api:slack-api-client-kotlin-extension:1.40.3")

Expected result:

I want to be able to post a message with an image in it (preferable, but not absolutely necessary that it is formatted using blocks) AND I absolutely need the reference to that message.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions