Get Chat Spaces Attachment File and Save in Google Drive using Google Apps Script

144 Views Asked by At

I have a simple chat bot written in Google Apps Script that responds to various /slash commands from users within our organisation.

I want to create a feature which can accept an attachment from the user, then upload that attachment into a specified Google Drive location.

I am running into issues with authentication, I think.

I have tried many things.

To begin with I thought it would be simple, as when a user sends a message to the bot, the chat event includes an object with data about the attachment, including a "downloadUri=https://chat.google.com/api/get_attachment_url?url_type=DOWNLOAD_URL&content_type=application/pdf&attachment_token={etc, etc}"

I thought, great! I can simply:

  //get the downloadUri
  let attachmentUrl = data.message.attachment[0].attachmentData.downloadUri

  // Download the attachment
  let attachment = UrlFetchApp.fetch(attachmentUrl).getBlob();

  // Save the attachment to Google Drive
  let file = DriveApp.createFile(attachment);

I thought, since this script already has oauthScopes such as:

"oauthScopes": [
    "https://www.googleapis.com/auth/chat.spaces",
    "https://www.googleapis.com/auth/chat.messages",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.external_request"
]

That it would simply be permitted to download that file.

But this just creates a file with all the HTML contents of the Google authentication page.

I tested by inputting that "attachmentUrl" into a browser and successfully download the file.

Then I got all caught up adding various auth scopes and methods to try to authenticate the script correctly.

I tried making a get request to the endpoint documented here: https://developers.google.com/chat/api/reference/rest/v1/spaces.messages.attachments/get

But it just returns another downloadUri, which if I try to retrieve that, ends up with the same problem (downloading an auth screen).

So I think after many hours I will turn to you for any help or suggestions you can offer.

I am wondering if I am perhaps just going about this the wrong way.

Thank you.

2

There are 2 best solutions below

0
On BEST ANSWER

I was going about this the wrong way.

I was attempting to download message.attachment[0].attachmentData.downloadUri

But as per the documentation downloadUri is only for human users:

"Output only. The download URL which should be used to allow a human user to download the attachment. Chat apps shouldn't use this URL to download attachment content."

Instead:

I needed to use the attachmentDataRef object which is subsequently used to make a request to the media API endpoint to then download the attachment data. This is documented here.

I hope this helps anyone else stuck on this who, like me, didn't read the documentation.

0
On

This is a solution I figured out. I initially posted it on Reddit, but I believe it will have better visibility here for those looking for a quick solution.

First of all, don't waste time on the thumbnailUri or the downloadUri.

From the Google official documentation:
Don't use the thumbnailUri and downloadUri fields to access the contents of the uploaded file from your app. Use the thumbnailUri field to preview the attachment for a human user. The downloadUri field is used to let a human user download the attachment.

  1. Using Chat API, get the attachmentDataRef.resourceName. Optionally, you can retrieve other information such as contentName, or contentType.
// You can use the Event object from the onMessage function
function onMessage(event) {
    const resourceName = event.message.attachment.attachmentDataRef.resourceName;
}

// Or you can use the Chat.Spaces.Messages.Attachments.get()
function getResourceName() {
    const myAttachmentLocation = "spaces/googleSpace/messages/messageId/attachments/attachmentId";
    const myAttachment = Chat.Spaces.Messages.Attachments.get(myAttachmentLocation, {}, {'Authorization' : `Bearer ${myBearerToken}`});
    return myAttachment.attachmentDataRef.resourceName;
}
  1. Get blob of attachment
function getAttachmentBlob(resourceName) {
    const url = `https://chat.googleapis.com/v1/media/${resourceName}?alt=media`;
    const headers = {"Authorization": `Bearer ${myBearerToken}`};
    const options = {
        "headers": headers,
        "method": "GET",
        "muteHttpExceptions": true
    };
    return UrlFetchApp.fetch(encodeURI(url), options).getBlob(); 
}
  1. Create a Drive File with the previous blob
function createFileOnDestination(blob) {
    const myDest = "folderId";
    
    // You can change your blob metadata
    blob.setName('myAttachment.png');
    blob.setContentType('image/png');

    DriveApp.getFolderById(myDest).createFile(blob);
}

I hope this saves you time!