Python: 'NoneType' object is unsubscriptable : imaplib - python

This is covered extensively in SO, so I apologize in advance ...however, I've gone through the posts and can't get this to work.
GOALS
Want to get email from gmail that match certain criteria, save the attachments, then delete them.
ISSUE
So, I can get everything to work except deleting the emails. It deletes a few then I get this error:
Traceback (most recent call last): File "get_overdues.py", line 22,
in
email_body = data[0][1] TypeError: 'NoneType' object is unsubscriptable
Every time I run it it deletes more emails then exits with the same error. This has to run on a cronjob and can't be babysat.
What am I doing wrong?
m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login(user,word)
m.select("INBOX")
searchString = "(SUBJECT \"Daily Mail Notices\")"
resp, items = m.search(None,searchString)
items = items[0].split()
for emailid in items:
print emailid
resp, data = m.fetch(emailid, "(RFC822)")
email_body = data[0][1]
mail = email.message_from_string(email_body)
if mail.get_content_maintype() != 'multipart':
continue
print "["+mail["From"]+"] :" + mail["Subject"] + mail["Date"]
sub_dir = re.sub('[,:\- ]','', mail["Date"])
for part in mail.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
message_dir = os.path.join(dump_dir, sub_dir)
if not os.path.exists(message_dir):
os.makedirs(message_dir)
filename = part.get_filename()
counter = 1
if not filename:
filename = 'overdues-%s' % counter
counter += 1
att_path = os.path.join(dump_dir, message_dir, filename)
if not os.path.isfile(att_path) :
fp = open(att_path, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
m.store(emailid, '+FLAGS', r'(\Deleted)')
m.expunge()
m.close()
m.logout()

Your problem is clearly with fetch:
resp, data = m.fetch(emailid, "(RFC822)")
email_body = data[0][1]
It's returning a NoneType for either data or, less likely, for data[0], and None obviously isn't subscriptable. You may want to double check the results of m.fetch and see if it's coming the form you expect it to.
This is probably because this email was deleted (and not expunged).

Related

Python - Get datetime of mails - Gmail

I was trying to download the attachments from the Gmail using python for a specific keyword and the code is below,
def read_email_from_gmail():
mail = imaplib.IMAP4_SSL(imap_url)
mail.login(user, password)
mail.select('"[Gmail]/All Mail"')
print("Login into Mailbox")
result, data = mail.search(None, '(SUBJECT "Contract note")')
count = 0
for num in data[0].split():
result, data = mail.fetch(num, "(RFC822)")
raw_email_string = data[0][1].decode('utf-8')
msg = email.message_from_string(raw_email_string)
for part in msg.walk():
if part.get_content_type() == "text/plain":
print(part.get_payload(decode=True))
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
fileName = part.get_filename()
print(part.get_)
fileName = fileName+str(count)+str('.pdf')
count = count + 1
if bool(fileName):
filePath = os.path.join(attachment_dir, fileName)
with open(filePath, 'wb') as e:
e.write(part.get_payload(decode=True))
The code works fine and it was downloading the attachment. The issue is all the attachment in the mails are off in the same name so in the above code I have added a count and appended it. But in future it will be so tough to find the right file.
Note:- I used to recieve the mail daily
Can someone please guide me how to get the date of the email so that I will append it to the filename rather than the count.
We can get the date of the email using
msg['Date']

Python imaplib delete's 2 emails then crashes

What I am trying to do:
I want to retrieve emails from gmail, download the email as a text file then delete the original email off the gmail server.
The Issue:
The Issue I run into is when I add the delete portion of the code. So If I don't delete, it will pull down all the emails I want and save them as text files. Once I added the delete portion of my code it downloads 2 emails then deletes them off the gmail server then gives the error below on the third. I can run the script again and it will download another 2 and give the same error on the third. I can't see what i'm doing wrong, any help with this would be greatly appreciated.
The Code
#!/usr/bin/env python
import getpass, imaplib, email, os
from email.parser import HeaderParser
detach_dir = os.path.expanduser('~/Documents/Test/')
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
address = "EMAIL#GMAIL.com"
password = "PASSWORD"
M.login(address, password)
M.select("EMAIL LABEL")
resp, items = M.search(None, "ALL")
items = items[0].split()
for emailid in items:
resp, data = M.FETCH(emailid, '(RFC822)')
mail = email.message_from_string(data[0][1])
for part in mail.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get_content_subtype() != 'plain':
continue
payload = part.get_payload()
filename = mail["Subject"] + "_gmail.txt"
filename = filename.replace('FW: ', '').replace(' ', '_').replace('\n', '').replace('\r', '').replace('__', '_')
print "FILENAME IS: " + filename
att_path = os.path.join(detach_dir, filename)
if (not os.path.isfile(att_path)):
fp = open(att_path, 'w+')
fp.write(payload)
fp.close()
M.store(emailid, '+FLAGS', '\\Deleted')
M.expunge()
The Error
FILENAME IS: email_1_gmail.txt
FILENAME IS: email_2_gmail.txt
Traceback (most recent call last):
File "/Users/user1/Documents/Personal Work/Project/gmail4.py", line 20, in <module>
mail = email.message_from_string(data[0][1])
TypeError: 'NoneType' object has no attribute '__getitem__'

Python. Imaplib moving mail to Trash

I have read some solutions but none apparently worked, maybe because of Gmail, I'm not sure, the thing is I want to move my emails from INBOX to Trash and this is what I do:
def process_mailbox():
message={}
M = imaplib.IMAP4_SSL('imap.gmail.com')
try:
M.login('myemail#gmail.com', 'mypassword')
except imaplib.IMAP4.error:
print "LOGIN FAILED!!! "
# ... exit or deal with failure...
rv, mailboxes = M.list()
print mailboxes
if rv == 'OK':
M.select("INBOX")
rv, data = M.search(None, "ALL")
if rv != 'OK':
print "No messages found!"
for num in data[0].split(): #Read all the mails
rv, data = M.fetch(num, '(RFC822)')
if rv != 'OK':
print "ERROR getting message", num
return
msg = email.message_from_string(data[0][1])
#print 'Subject %s: %s' % (num, msg['Subject'])
message['Subject']=msg['Subject']
print 'Subject: '+message['Subject']
if msg.get_content_type() == "text_plain": #No Multipart messages
body = msg.get_payload()
message['Body']=body
else: #Multipart messages
for part in msg.walk():
if part.get_content_type() == "text/plain": # ignore attachments/html
message['Body']=body
#print message['Body']
date_tuple = email.utils.parsedate_tz(msg['Date'])
if date_tuple:
local_date = datetime.datetime.fromtimestamp(
email.utils.mktime_tz(date_tuple))
print "Local Date:", local_date.strftime("%Y-%m-%d")
message['Date']=local_date.strftime("%Y-%m-%d")
#send_mail(message)
#insert_vulnerability_mail(message['Subject'],message['Date'],message['Body'].encode('utf-8'))
# M.store(num, '+FLAGS', '\\Deleted')
M.copy(num,'[Gmail]/Trash')
M.close()
M.logout()
So, as you can see, is: M.copy(num,'[Gmail]/Trash') and the result is that I move some emails, lets say, if I have 7 I move 4 out of 7, then I get this error:
Traceback (most recent call last):
File "mail.py", line 116, in <module>
process_mailbox()
File "mail.py", line 75, in process_mailbox
msg = email.message_from_string(data[0][1])
TypeError: 'NoneType' object has no attribute '__getitem__'
Which I don't understand because when I execute next time the programm I move more emails, get the error on an other email, execute and I end up moving everything, but I have to execute several times.
Does anyone know what is going on¿? Thank you on advance
Try this:
imap.store(mail_to_be_deleted, '+FLAGS', r'(\Deleted)')

Downloading Attachment and Marking Mail as Unseen

I want to download the attachments from Unread Messages, but also does not want the messages to be flagged Seen.
The below code works, but currently setting the mail as Seen
Tried '(BODY.PEEK[HEADER])' , but then even mail download stopped.
import upload,checkFileAtServer,sha1sum,email, getpass, imaplib, os
detach_dir = '.'
m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login('myaccount#gmail.com','password')
m.select("inbox")
resp, items = m.search(None, "(UNSEEN)")
items = items[0].split()
for emailid in items:
#resp, data = m.fetch(emailid, '(BODY.PEEK[HEADER])')
resp, data = m.fetch(emailid, "(RFC822)")
email_body = data[0][1]
mail = email.message_from_string(email_body)
temp = m.store(emailid,'+FLAGS', '\\Seen')
m.expunge()
if mail.get_content_maintype() != 'multipart':
continue
print "["+mail["From"]+"] :" + mail["Subject"]
for part in mail.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
filename = part.get_filename()
att_path = os.path.join(detach_dir, filename)
if not os.path.isfile(att_path) :
fp = open(att_path, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
sha1sum = sha1sum.calculateSHA1(att_path)
print type(sha1sum)
responseFromServer = checkFileAtServer.responseFromServer(sha1sum)
if(responseFromServer == "NOT_CHECKED"):
upload.uploadToSecureServer('root','root',att_path,att_path)
Anybody can guide me what am I missing ?
Thanks.
If you do not want to mark a message as \Seen, don't call the STORE IMAP command and don't use FETCHable items which are documented to cause an implicit marking as such (yes, the RFC822 is an alias for BODY[] which causes the message to be marked as read).

Downloading multiple attachments using imaplib

How can I download multiple attachments from a single mail using imaplib?
Let's say I have an e-mail and that e-mail contains 4 attachments. How can I download all of those attachments? The code below only downloads a single attachment from an e-mail.
detach_dir = 'c:/downloads'
m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login('hello#gmail.com','3323434')
m.select("[Gmail]/All Mail")
resp, items = m.search(None, "(UNSEEN)")
items = items[0].split()
for emailid in items:
resp, data = m.fetch(emailid, "(RFC822)")
email_body = data[0][1]
mail = email.message_from_string(email_body)
temp = m.store(emailid,'+FLAGS', '\\Seen')
m.expunge()
if mail.get_content_maintype() != 'multipart':
continue
print "["+mail["From"]+"] :" + mail["Subject"]
for part in mail.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
filename = part.get_filename()
att_path = os.path.join(detach_dir, filename)
if not os.path.isfile(att_path) :
fp = open(att_path, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
return HttpResponse('check folder')
For any future python travellers.
Here is a class that downloads any attachment found for an email and saves it to a specific location.
import email
import imaplib
import os
class FetchEmail():
connection = None
error = None
def __init__(self, mail_server, username, password):
self.connection = imaplib.IMAP4_SSL(mail_server)
self.connection.login(username, password)
self.connection.select(readonly=False) # so we can mark mails as read
def close_connection(self):
"""
Close the connection to the IMAP server
"""
self.connection.close()
def save_attachment(self, msg, download_folder="/tmp"):
"""
Given a message, save its attachments to the specified
download folder (default is /tmp)
return: file path to attachment
"""
att_path = "No attachment found."
for part in msg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
filename = part.get_filename()
att_path = os.path.join(download_folder, filename)
if not os.path.isfile(att_path):
fp = open(att_path, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
return att_path
def fetch_unread_messages(self):
"""
Retrieve unread messages
"""
emails = []
(result, messages) = self.connection.search(None, 'UnSeen')
if result == "OK":
for message in messages[0].split(' '):
try:
ret, data = self.connection.fetch(message,'(RFC822)')
except:
print "No new emails to read."
self.close_connection()
exit()
msg = email.message_from_bytes(data[0][1])
if isinstance(msg, str) == False:
emails.append(msg)
response, data = self.connection.store(message, '+FLAGS','\\Seen')
return emails
self.error = "Failed to retreive emails."
return emails
def parse_email_address(self, email_address):
"""
Helper function to parse out the email address from the message
return: tuple (name, address). Eg. ('John Doe', 'jdoe#example.com')
"""
return email.utils.parseaddr(email_address)
I reworked the code, breaking it up into functions. I use PEEK so I don't change the UNREAD status of the email messages.
I'm posting my take on the problem, similar to #John, but I use only functions instead of classes:
import imaplib
import email
# Connect to an IMAP server
def connect(server, user, password):
m = imaplib.IMAP4_SSL(server)
m.login(user, password)
m.select()
return m
# Download all attachment files for a given email
def downloaAttachmentsInEmail(m, emailid, outputdir):
resp, data = m.fetch(emailid, "(BODY.PEEK[])")
email_body = data[0][1]
mail = email.message_from_string(email_body)
if mail.get_content_maintype() != 'multipart':
return
for part in mail.walk():
if part.get_content_maintype() != 'multipart' and part.get('Content-Disposition') is not None:
open(outputdir + '/' + part.get_filename(), 'wb').write(part.get_payload(decode=True))
# Download all the attachment files for all emails in the inbox.
def downloadAllAttachmentsInInbox(server, user, password, outputdir):
m = connect(server, user, password)
resp, items = m.search(None, "(ALL)")
items = items[0].split()
for emailid in items:
downloaAttachmentsInEmail(m, emailid, outputdir)
You code appears okay except for the return (perhaps a typo?) right after the fp.close():
...
fp.write(part.get_payload(decode=True))
fp.close()
return HttpResponse('check folder')
After saving the first attachment it returns from the function. Comment out that line and see if it fixes your issue.
You may use imap_tools package:
https://pypi.org/project/imap-tools/
from imap_tools import MailBox
with MailBox('imap.mail.com').login('test#mail.com', 'password', 'INBOX') as mailbox:
for message in mailbox.fetch():
for att in message.attachments: # list: [Attachment objects]
att.filename # str: 'cat.jpg'
att.content_type # str: 'image/jpeg'
att.payload # bytes: b'\xff\xd8\xff\xe0\'
* You can try following function to get mail attachment
def create_message_attachment(self,msg_str):
count = 1
body = ''
content_id = ''
for part in msg_str.walk():
file_name_gl = None
mptype = part.get_content_maintype()
file_name_gl = part.get_filename()
if mptype == "multipart":
continue
elif mptype == "text":
if not file_name_gl: continue
elif mptype == "image":
content_id = part.get('Content-ID')
if not file_name_gl:
file_name_gl = 'image_' + str(count) + '.' + part.get_content_subtype()
count = count + 1
body = part.get_payload(decode = True)
if type(body) <> type(None) :
body = body.strip()
if body <> "":
body = base64.encodestring(body)
#sashoalm 's code worked for me with a minor change:
change mail = email.message_from_string(email_body) in downloaAttachmentsInEmail to mail = email.message_from_bytes(email_body)
I was getting an error when trying to read bytes (the attachment) as a string. Now it works perfectly for me.
Heres a full example of the code:
server = 'outlook.office365.com'
user = 'YOUR USERNAME'
password = 'YOUR PASSWORD'
outputdir = 'DIRECTORY THAT YOU WANT FILES DOWNLOADED TO'
subject = 'Data Exports' #subject line of the emails you want to download attachments from
def connect(server, user, password):
m = imaplib.IMAP4_SSL(server)
m.login(user, password)
m.select()
return m
def downloaAttachmentsInEmail(m, emailid, outputdir):
resp, data = m.fetch(emailid, "(BODY.PEEK[])")
email_body = data[0][1]
mail = email.message_from_bytes(email_body)
if mail.get_content_maintype() != 'multipart':
return
for part in mail.walk():
if part.get_content_maintype() != 'multipart' and part.get('Content-Disposition') is not None:
open(outputdir + '/' + part.get_filename(), 'wb').write(part.get_payload(decode=True))
#download attachments from all emails with a specified subject line
def downloadAttachments(subject):
m = connect(server, user, password)
m.select("Inbox")
typ, msgs = m.search(None, '(SUBJECT "' + subject + '")')
msgs = msgs[0].split()
for emailid in msgs:
downloaAttachmentsInEmail(m, emailid, outputdir)
downloadAttachments(subject)
import re
def get_valid_filename(s):
s = str(s).strip().replace(' ', '_')
return re.sub(r'(?u)[^-\w.]', '', s)
fileName = get_valid_filename(part.get_filename())
Clean up the file name if it contains invalid characters. e.g: : on Windows.

Categories