-2

Hi so I have this program which basically goes into the inbox and then looks at the latest email. Scans it and if it has a link it gives you a notification, or if it has an attachment it gives a notification. Everything is working fine although I tried to combine the two notifications into one. So it's a bit nicer, instead of having one notification show, then another right after it. If an email has both a link and attachment. I'd of prefer it just to be one notification if the email has both and then separate notifications if they only contain either a link or attachment. I've tried to split it up into a if and elif loop.

I can get a notification to appear for both if I do an 'and' within the loop in the if statement - e.g

if any(word in html_text for word in word) and file_name == None:
    fboth()

fboth() (this is the function that would tell you there is both a link and attachment, just like flink() and fattach())

although when I put my other options as elif: statements, one for a link by itself and one for an attachment by itself. Those still play out even though it found the first statement to be True and it alerts that the email has both a link and attachment. Is there away to stop this? I'm starting to think it's another if statement? But I had a few failed attempts and maybe not fully understanding it. Is my theory correct with how I am tackling it by still using the for loop?

Here is the code:

import imaplib
import email
import Tkinter as tk
import time

word = ["href=", "href", "<a href="] #list of strings to search for in email body

#connection to the email server
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('xxxx', 'xxxx')
mail.list()


latest_email_uid = ''

#Finding a link popup
def flink():
    flink = tk.Tk()
    flink.title("Found Link")

    tk.Label(flink, text="Email has link in body\n" + "From: " + msg['From'] + "\n" + "Subject: " + msg['Subject'] + "\n" + "Date: " + msg['Date'], fg='yellow', bg='black').pack()
    flink.after(5000, lambda: flink.destroy())     # time in ms
    flink.mainloop()

#Finding an attachment popup
def fattach():
    fattach = tk.Tk()
    fattach.title("Found Attachment")

    tk.Label(fattach, text= " Latest Email has attachment\n" + "Filename: " + file_name + "\n" + "Fron: " + msg['From'] + "\n" + "Subject: " + msg['Subject'] + "\n" + "Date: " + msg['Date'], fg='yellow', bg='black').pack()
    fattach.after(5000, lambda: fattach.destroy())     # time in ms
    fattach.mainloop()

while True:
    mail.select("Inbox", readonly=True) # connect to inbox.
    result, data = mail.uid('search', None, "ALL") # search and return uids instead
    ids = data[0] # data is a list.
    id_list = ids.split() # ids is a space separated string

    if data[0].split()[-1] == latest_email_uid:
        time.sleep(120) # value here once per minute is already considered fairly aggressive by many IMAP server admins. Depending on your application and expected

    else:
        latest_email_uid = data[0].split()[-1]
        result, data = mail.uid('fetch', latest_email_uid, '(RFC822)') # fetch the email headers and body (RFC822) for the given ID7
        raw_email = data[0][1]

        # "---------------------------------------------------------"
        # "Are there links in the email?"
        # "---------------------------------------------------------"

        msg = email.message_from_string(raw_email)
        for part in msg.walk():
            # each part is a either non-multipart, or another multipart message
            # that contains further parts... Message is organized like a tree
            if part.get_content_type() == 'text/html':
                html_text = part.get_payload()

                if any(word in html_text for word in word):
                    flink()
                else:
                    pass

        # "---------------------------------------------------------"
        # "Are there attachments?"
        # "---------------------------------------------------------"

        for part in msg.walk():
            attach = part.get_content_type()
            file_name = part.get_filename()

            if file_name == None:
                pass
            else:
                fattach()

        mail.close()

        time.sleep(120)

EDIT: Ok so I got this far and I can get the loop to find a message with just a link in the email body (the first elif) yet nothing else works. As when sending an attachment that should make my file_name != to None now, yes?

        msg = email.message_from_string(raw_email)
        for part in msg.walk():
            attach = part.get_content_type()
            file_name = part.get_filename()
            # each part is a either non-multipart, or another multipart message
            # that contains further parts... Message is organized like a tree
            if part.get_content_type() == 'text/html':
                html_text = part.get_payload()
                text = any(word in html_text for word in word)

                if text == True and file_name != None:
                    print "file name and attachment"
                elif text == True and file_name == None:
                    print "Just link in text"
                elif text == False and file_name != None:
                    print "just an attachment"
idwithin
  • 47
  • 1
  • 2
  • 8
  • Briefly, set a flag to `True` instead of displaying the message immediately, then when you have examined the parts, display the message if the flag variable is `True`. Remember to set it back to `False` before scanning the next message (perhaps simply by always setting it to that at the top of the loop). – tripleee Mar 04 '18 at 15:13
  • I think I get somewhere with True and False statements although it only works for an email that has a link. Not if there is an attachment. I'm not sure what I'm doing wrong but it looks right to me. I'll put an update and the code in the main question. – idwithin Mar 04 '18 at 15:54
  • Your `elif` in the updated script is inside the `text/html` processing, you need to outdent it one level and adapt the logic accordingly. – tripleee Mar 04 '18 at 18:11

1 Answers1

0

You are needlessly doing the walk twice. Just call both functions within the same loop.

    maybe = False
    msg = email.message_from_string(raw_email)
    for part in msg.walk():
        if part.get_content_type() == 'text/html':
            html_text = part.get_payload()

            if any(word in html_text for word in word):
                maybe = True
        else:
            attach = part.get_content_type()
            file_name = part.get_filename()
            if file_name == None:
                pass
            else:
                maybe = True

    mail.close()
    if maybe:
        # display message

If you want the message to reflect in more detail whether there was a link or an attachment or both, you can change the maybe flag variable from a simple boolean to e.g. a set which you pass to the message display function to indicate what strings exactly to populate the message dialog with.

tripleee
  • 175,061
  • 34
  • 275
  • 318