24

I am using Gmail API to access my Gmail data and Google Python API client.

According to documentation to get the message attachment they gave one sample for Python. But the same code I tried then I am getting an error:

AttributeError: 'Resource' object has no attribute 'user'

The line where I am getting error:

message = service.user().messages().get(userId=user_id, id=msg_id).execute()

So I tried users() by replacing user():

message = service.users().messages().get(userId=user_id, id=msg_id).execute()

But I am not getting part['body']['data'] in for part in message['payload']['parts'].

informatik01
  • 16,038
  • 10
  • 74
  • 104
Rahul Kulhari
  • 1,115
  • 1
  • 15
  • 44

7 Answers7

54

Expanding @Eric answer, I wrote the following corrected version of GetAttachments function from the docs:

# based on Python example from 
# https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get
# which is licensed under Apache 2.0 License

import base64
from apiclient import errors

def GetAttachments(service, user_id, msg_id):
    """Get and store attachment from Message with given id.

    :param service: Authorized Gmail API service instance.
    :param user_id: User's email address. The special value "me" can be used to indicate the authenticated user.
    :param msg_id: ID of Message containing attachment.
    """
    try:
        message = service.users().messages().get(userId=user_id, id=msg_id).execute()

        for part in message['payload']['parts']:
            if part['filename']:
                if 'data' in part['body']:
                    data = part['body']['data']
                else:
                    att_id = part['body']['attachmentId']
                    att = service.users().messages().attachments().get(userId=user_id, messageId=msg_id,id=att_id).execute()
                    data = att['data']
                file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))
                path = part['filename']

                with open(path, 'w') as f:
                    f.write(file_data)

    except errors.HttpError, error:
        print 'An error occurred: %s' % error
Community
  • 1
  • 1
Ilya V. Schurov
  • 7,687
  • 2
  • 40
  • 78
11

You can still miss attachments by following @Ilya V. Schurov or @Cam T answers, the reason is because the email structure can be different based on the mimeType.

Inspired by this answer, here is my approach to the problem.

import base64
from apiclient import errors

def GetAttachments(service, user_id, msg_id, store_dir=""):
    """Get and store attachment from Message with given id.
        Args:
            service: Authorized Gmail API service instance.
            user_id: User's email address. The special value "me"
                can be used to indicate the authenticated user.
            msg_id: ID of Message containing attachment.
            store_dir: The directory used to store attachments.
    """
    try:
        message = service.users().messages().get(userId=user_id, id=msg_id).execute()
        parts = [message['payload']]
        while parts:
            part = parts.pop()
            if part.get('parts'):
                parts.extend(part['parts'])
            if part.get('filename'):
                if 'data' in part['body']:
                    file_data = base64.urlsafe_b64decode(part['body']['data'].encode('UTF-8'))
                    #self.stdout.write('FileData for %s, %s found! size: %s' % (message['id'], part['filename'], part['size']))
                elif 'attachmentId' in part['body']:
                    attachment = service.users().messages().attachments().get(
                        userId=user_id, messageId=message['id'], id=part['body']['attachmentId']
                    ).execute()
                    file_data = base64.urlsafe_b64decode(attachment['data'].encode('UTF-8'))
                    #self.stdout.write('FileData for %s, %s found! size: %s' % (message['id'], part['filename'], attachment['size']))
                else:
                    file_data = None
                if file_data:
                    #do some staff, e.g.
                    path = ''.join([store_dir, part['filename']])
                    with open(path, 'w') as f:
                        f.write(file_data)
    except errors.HttpError as error:
        print 'An error occurred: %s' % error
Todor
  • 15,307
  • 5
  • 55
  • 62
  • How are you dealing with those missed attachments? All I see is a `file_data = None` which then does nothing with it. – gdvalderrama Sep 20 '17 at 11:57
  • Take a look at the `while` statement this is where difference comes from. The last `else: file_data = None` is just for code safety. – Todor Sep 20 '17 at 12:25
  • 1
    ah, I see, the difference is you also also deal with the data in the upper most level (`payload['body']['data']`) while other answers only look at the body within the parts (`payload['parts']`) – gdvalderrama Sep 20 '17 at 13:57
  • 1
    'wb' instead of 'w' – CodeFarmer Jan 08 '19 at 04:42
  • I am able to download the pdf attachments using your method but when I open those pdfs, they don't open. I get the error 'THERE WAS AN ERROR OPENING THIS DOCUMENT. THE FILE IS DAMAGED AND COULDN'T BE REPAIRED' – KawaiKx Jul 21 '21 at 08:34
9

i tested codes above and doesn't worked. And i updated some stuff for other posts. WriteFileError

    import base64
    from apiclient import errors


    def GetAttachments(service, user_id, msg_id, prefix=""):
       """Get and store attachment from Message with given id.

       Args:
       service: Authorized Gmail API service instance.
       user_id: User's email address. The special value "me"
       can be used to indicate the authenticated user.
       msg_id: ID of Message containing attachment.
       prefix: prefix which is added to the attachment filename on saving
       """
       try:
           message = service.users().messages().get(userId=user_id, id=msg_id).execute()

           for part in message['payload'].get('parts', ''):
              if part['filename']:
                  if 'data' in part['body']:
                     data=part['body']['data']
                  else:
                     att_id=part['body']['attachmentId']
                     att=service.users().messages().attachments().get(userId=user_id, messageId=msg_id,id=att_id).execute()
                     data=att['data']
            file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))
            path = prefix+part['filename']

            with open(path, 'wb') as f:
                f.write(file_data)

        except errors.HttpError as error:
            print('An error occurred: %s' % error)
f9n
  • 93
  • 1
  • 5
4

It's definitely users().

The format of the response Message is largely dependent on the format parameter you use. If you use the default (FULL), then parts will either have part['body']['data'] or, when data is large, with an attachment_id field that you can pass to messages().attachments().get().

If you look at the attachments docs you'll see this: https://developers.google.com/gmail/api/v1/reference/users/messages/attachments

(Would be nice if this was also mentioned on the main messages docs page also.)

informatik01
  • 16,038
  • 10
  • 74
  • 104
Eric D
  • 6,901
  • 1
  • 15
  • 26
4

Thanks to @Ilya V. Schurov and @Todor for the answers. You can still miss messages in the case when there are mails with and without attachment for the same search string. Here is my approach to getting mail body for both type of mails i.e. with attachment and without attachment.

def get_attachments(service, msg_id):
try:
    message = service.users().messages().get(userId='me', id=msg_id).execute()

    for part in message['payload']['parts']:
        if part['filename']:
            if 'data' in part['body']:
                data = part['body']['data']
            else:
                att_id = part['body']['attachmentId']
                att = service.users().messages().attachments().get(userId='me', messageId=msg_id,id=att_id).execute()
                data = att['data']
            file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))
            path = part['filename']

            with open(path, 'wb') as f:
                f.write(file_data)
    

except errors.HttpError as error:
    print ('An error occurred: %s') % error

def get_message(service,msg_id):
try:
    message = service.users().messages().get(userId='me', id=msg_id).execute()
    if message['payload']['mimeType'] == 'multipart/mixed':
        for part in message['payload']['parts']:
            for sub_part in part['parts']:
                if sub_part['mimeType'] == 'text/plain':
                    data = sub_part['body']['data']
                    break
            if data:
                break           
    else:
        for part in message['payload']['parts']:
            if part['mimeType'] == 'text/plain':
                data = part['body']['data']
                break
    
    content = base64.b64decode(data).decode('utf-8')
    print(content)

    return content    

except errors.HttpError as error:
    print("An error occured : %s") %error
2

I made the following changes for the code above and works totally fine for every email id contains attachment documents, I hope this can help because with the API example you will get an error Key.

def GetAttachments(service, user_id, msg_id, store_dir):

"""Get and store attachment from Message with given id.

Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
msg_id: ID of Message containing attachment.
prefix: prefix which is added to the attachment filename on saving
"""
try:
    message = service.users().messages().get(userId=user_id, id=msg_id).execute()
    for part in message['payload']['parts']:
        newvar = part['body']
        if 'attachmentId' in newvar:
            att_id = newvar['attachmentId']
            att = service.users().messages().attachments().get(userId=user_id, messageId=msg_id, id=att_id).execute()
            data = att['data']
            file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))
            print(part['filename'])
            path = ''.join([store_dir, part['filename']])
            f = open(path, 'wb')
            f.write(file_data)
            f.close()
except errors.HttpError, error:
    print 'An error occurred: %s' % error

Google Official API for Attachments

Todor
  • 15,307
  • 5
  • 55
  • 62
Cam T
  • 1,965
  • 2
  • 8
  • 8
1
from __future__ import print_function
import base64
import os.path
import oauth2client
from googleapiclient.discovery import build
from oauth2client import file,tools
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
store_dir=os.getcwd()

def attachment_download():

store = oauth2client.file.Storage('credentials_gmail.json')
creds = store.get()
if not creds or creds.invalid:
    flow = oauth2client.client.flow_from_clientsecrets('client_secrets.json', SCOPES)
    creds = oauth2client.tools.run_flow(flow, store)


try:
    service = build('gmail', 'v1', credentials=creds)
    results = service.users().messages().list(userId='me', labelIds=['XXXX']).execute() # XXXX is label id use INBOX to download from inbox
    messages = results.get('messages', [])
    for message in messages:
        msg = service.users().messages().get(userId='me', id=message['id']).execute()
        for part in msg['payload'].get('parts', ''):

            if part['filename']:
                if 'data' in part['body']:
                    data = part['body']['data']
                else:
                    att_id = part['body']['attachmentId']
                    att = service.users().messages().attachments().get(userId='me', messageId=message['id'],id=att_id).execute()
                    data = att['data']
                file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))

                filename = part['filename']
                print(filename)
                path = os.path.join(store_dir + '\\' 'Downloaded files' + '\\' + filename)

                with open(path, 'wb') as f:
                    f.write(file_data)
                    f.close()
except Exception as error:
    print(error)

Please See: To get label id https://developers.google.com/gmail/api/v1/reference/users/labels/list

Rocky Mohan
  • 41
  • 1
  • 6