1

Continuation of this program Reference list index to enumerate value

This program will search a dictionary of senders against various criteria in outlook (msg.sender msg.subject msg.SenderEmailAddress etc), then download attachments from ALL senders matching the dictionary and criteria. This works fine and I've been using it for a while.

#Code before this allows me to select a date range to search

senderDict = {sender email : sender name}
listSender = list(senderDict.values())
listSender.insert(0, "Search Any")

########### Choose option from listSender
while True:     
        try:
            senderNum = int(input("Choose a sender: "))
            senderChoice = listSender[senderNum] #refer senderNum to list index

            #Search all
            if senderNum == 0:
                    print("Searching for any sender")
                    break


            elif senderNum <= len(listSender) and senderNum > 0:
                    print("you chose", senderChoice)
                    break

            elif senderNum < 0:
                    print("Cannot choose a negative number.")
                    continue


############# CODE TO CHECK SENDER ATTACHMENTS
for msg in reversed(itcontents): #reversed() will go from most recent to oldest email based on date

########## Search for ANY sender
    if msg.Class == 43: #only search mail items (class 43)
            try:                        
                    if ( senderNum == 0 and
                            (str(msg.SenderEmailAddress) or str(msg.Subject) or str(msg.Sender) or str(msg.SentOnBehalfOfName)) in senderDict 
                            and
                             #msg.SentOn date is not older than searchDate or newer than startDate
                            (msg.SentOn.date() >= searchDate and msg.SentOn.date() <= startDate.date())
                        ):
                            check += 1
                            print(check, "messages from", msg.SenderEmailAddress, "on", msg.SentOn.date()) #keep count of checked messages

                            #Check attachment file format string, invoices are usually PDFs.
                            #x refers to the attachment. Not every message from a listed sender has an attachment.
                            for x in msg.Attachments: 
                                    if str(".pdf").casefold() in str(x): #casfold() checks possible upper or lower case combinations e.g PdF or pDf
                                            x.SaveAsFile(r"C:\Users\camerona\Desktop\Invoices from Outlook\\" + str(msg.SentOn.date()) + str(msg.SenderEmailAddress) + x.FileName)
                                            print("Saved attachment", x, "from", str(msg.Sender()), "on", str(msg.SentOn.date()))

It works fine up to here.

Now I am trying to add functionality to only save attachments from a SPECIFIC sender chosen from an enumerated list, created from the dictionary. The process as I understand it: I want to check if the chosen sender string matches the criteria and is in the dictionary. I've tried using the same code for searching ANY.

########## Search for SPECIFIC sender
               elif ( str(senderChoice) in ( str(msg.SenderEmailAddress) or str(msg.Subject) or str(msg.Sender) or str(msg.SentOnBehalfOfName)) in senderDict
                        and
                        (msg.SentOn.date() >= searchDate and msg.SentOn.date() <= startDate.date())
                              ):
                                check += 1
                                print(check, "messages from", msg.SenderEmailAddress, "on", msg.SentOn.date()) #keep count of checked messages

                                #Check attachment file format, invoices are usually PDFs
                                #x refers to the attachment. Not every message from a listed sender has an attachment.
                                for x in msg.Attachments: 
                                        if str(".pdf").casefold() in str(x): #casfold() cheks upper or lower case format
                                                x.SaveAsFile(r"C:\Users\camerona\Desktop\Invoices from Outlook\\" + str(msg.SentOn.date()) + str(msg.SenderEmailAddress) + x.FileName)
                                                print("Saved attachment", x, "from", str(msg.Sender()), "on", str(msg.SentOn.date()))

Output (redacted) looks like this for each attachment:

Choose a sender: 0
Searching for any sender
1 messages from xxxxxxx.com on 2019-12-16
Saved attachment Invoice INV-001172.pdf from xxxxxxxxx on 2019-12-16

Then choosing that sender specifically does nothing:

Choose a sender: 1
you chose xxxxxx
(attachment sumamry should appear like above)

I'm not sure why this isn't working. I feel like it must be something to do with the first line of the elif statement for str(senderChoice).

  • What are all the `or' statements meant to do in that last snippet? What’s the point of a `try` statement without an `except`? Also, variable and function names should follow the `lower_case_with_underscores` style. Can you share all of your code/data? I will try to post a refactored version of this tomorrow :) – AMC Dec 17 '19 at 04:52
  • What are the types of `msg.Sender`, `msg.SentOn`, etc.? – AMC Dec 17 '19 at 05:01
  • @Alexander Cécile there are a couple of exceptions but I didn't include them in the snippet. One was for a unicode error when a sender includes stuff like emojis in their name or subject line. msg.sender etc are msg.Class == 43, which is a 'mail item' according to outlook documentation. The or statements are there because some of the senders have addresses or sender names which are totally unrelated to the name of the company, and some of the emails are bounced from another address covered by msg.SentOnBehalfOfName. Is it possible to have multiple values for a single dictionary key? – CameronAtkinson Dec 17 '19 at 06:39
  • I meant how are the or statements being used. Are you trying to check if any of the values are in a dictionary? I’m not on a computer so it’s hard for me to tell. As for the `msg.Sender` etc., I was asking because I would have assumed that they were already strings, yet your code casts them. – AMC Dec 17 '19 at 06:40
  • This is my first time making something to use with outlook, based on this experience the output is weird sometimes if I don't use str(): "3 messages from /O=EXCHANGELABS/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=BAFEDE5E7884499B897994500FDB7E12-IT DEPARTME on 2019-12-16" The or statements are so that if the dictionary value is somewhere within the subject, sender name, email address, etc it will catch it. The company name or sender isn't always in the same spot when looking at the mail object. – CameronAtkinson Dec 17 '19 at 06:45

1 Answers1

1

Try this in your second block instead mate:

########## Search for SPECIFIC sender
               elif ( senderNum != 0 and
                            (str(msg.SenderEmailAddress) or str(msg.Subject) or str(msg.Sender) or str(msg.SentOnBehalfOfName)) in senderDict 
                            and
                             #msg.SentOn date is not older than searchDate or newer than startDate
                            (msg.SentOn.date() >= searchDate and msg.SentOn.date() <= startDate.date())
                            and
                             #NEW CODE TO CHECK IF THIS IS THE CHOSEN SPECIFIC USER
                            any([field for field in [str(msg.SenderEmailAddress), str(msg.Subject), str(msg.Sender), str(msg.SentOnBehalfOfName)] if str(senderChoice) in field])
                        ):
                                check += 1
                                print(check, "messages from", msg.SenderEmailAddress, "on", msg.SentOn.date()) #keep count of checked messages

                                #Check attachment file format, invoices are usually PDFs
                                #x refers to the attachment. Not every message from a listed sender has an attachment.
                                for x in msg.Attachments: 
                                        if str(".pdf").casefold() in str(x): #casfold() cheks upper or lower case format
                                                x.SaveAsFile(r"C:\Users\camerona\Desktop\Invoices from Outlook\\" + str(msg.SentOn.date()) + str(msg.SenderEmailAddress) + x.FileName)
                                                print("Saved attachment", x, "from", str(msg.Sender()), "on", str(msg.SentOn.date()))
Nick Cruz
  • 66
  • 4
  • That seems to have helped, I hadn't encountered the any() function yet. Is there a name for the '[field for field in [thing]]' technique you've used within it? – CameronAtkinson Dec 17 '19 at 04:07
  • @CameronAtkinson You might be looking for the term “lost comprehension”. I think here he actually should have used a generator expression though. – AMC Dec 17 '19 at 04:49
  • Using `casefold` is good, but shouldn’t it also be used on the other string? Or, even better, aren’t there libraries for checking file formats? – AMC Dec 17 '19 at 04:53
  • 1
    any() takes in a list of booleans and if any of them are True, it returns True. To generate the list of booleans I remembered I encountered a similar issue in the post below :) https://stackoverflow.com/questions/2104305/finding-elements-not-in-a-list – Nick Cruz Dec 17 '19 at 05:20
  • Now that I look at it I do see that it's a list comprehension. – CameronAtkinson Dec 17 '19 at 06:53