Python issues with specific outlook email - python-3.x

Can anyone give me advice for this issue? I am not getting any error messages and don't know where to start.
I made a program that checks emails in an Outlook inbox for pdf attachments from a list of senders, then saves the attachments to a folder. You put in a starting date and choose how many days you want to search through. There is a line in the code to show all emails being checked even if they aren't related to the sender/s for debugging. It has been working fine for months, until recently I got an email from ebay that contains an emote of a packed cardboard box in the subject line, and this specific email causes the program to misbehave.
Here's an example of the erroneous output - I've put in zero to search for 'any sender' from a list, and chosen a date range like always. The first Attribute Error is caused by an undeliverable message (which is not considered class 43 'mail' by outlook). You can see that the first normal mail item in the date range gets checked, with the subject line etc displayed, but then the next message is the ebay email, which is way outside the date range. The program skips over several days without checking any other emails in that range and ends the search. Same thing happens if I disable the display of every email in output (all emails still get read by the program, but there is a line to display only relevant ones).
There seems to be some issue with that specific email - Ebay is not one of the senders in the list the program checks for attachments, and even if I have a date range of zero days it will still show that email from a week ago as the last email checked. The only thing that seems unique about it is the cardboard box emote in the subject line, there is nothing else about it that stands out to me.
Here is the output:
Choose a sender (blank to exit): 0
Enter a starting search date in format dd-mm-yyyy (blank to exit): 05-05-2020
Enter how many days previous to search through: 3
Your search starts from 2020-05-05 00:00:00 and ends on 2020-05-02
Searching for any sender
Searching...
Attribute error (not class 43 mail) for item at mainChoice0() - .MessageClass REPORT.IPM.Note.NDR - Class 46
Looking at [...], WordPress has been updated on [...]. 2020-05-05
Looking at 📦 ORDER DELIVERED: 10x Capacitive Stylus Pen Tou… 2020-04-28
Reached end of date range: final message - 📦 ORDER DELIVERED: 10x Capacitive Stylus Pen Tou… 2020-04-28
If I move the problematic email to a different inbox then my program will correctly check emails across the whole date range I entered.
It gets weirder, because if put in a date range that would include that particular ebay email, then it DOES NOT appear in the output, while OTHER ebay emails which include emotes like a truck or a ticked box DO get checked by the program.
Looking at 🚚 ORDER UPDATE: 10x Capacitive Stylu... 2020-04-22
Looking at FAILED Schemus mail synchronization 2020-04-22
Attribute error (not class 43 mail) for item at mainChoice0() - .MessageClass REPORT.IPM.Note.NDR - Class 46
Attribute error (not class 43 mail) for item at mainChoice0() - .MessageClass REPORT.IPM.Note.NDR - Class 46
Looking at Active Directory Sync Failure 2020-04-21
Looking at ✅ ORDER CONFIRMED: 10x Capacitive Stylu... 2020-04-21
Below is the block of code for collecting attachments:
def mainChoice0(): #COLLECT ATTACHMENTS
#Choose sender from menu.
senderNum = input("Choose a sender (blank to exit): ")
if senderNum == "":
mainChoiceDisplay0 = False
topMenu() #Return to main menu
else:
senderNum = int(senderNum)
senderChoice = listSender[senderNum]
####Determine search range by date
startDate = input("Enter a starting search date in format dd-mm-yyyy (blank to exit): ")
if startDate == "":
mainChoiceDisplay0 = False
topMenu()
#strptime will convert startDate from a string to a usable date
startDate = datetime.datetime.strptime(startDate, "%d-%m-%Y")
searchRange = int(input("Enter how many days previous to search through: "))
searchDate = startDate.date() - datetime.timedelta(days = searchRange)
print("Your search starts from", startDate, "and ends on", searchDate, "\n")
#SEARCH ANY SENDERS
if senderNum == 0:
print("Searching for any sender")
print("Searching...")
#LOOK AT INBOX CONTENTS
for msg in reversed(itcontents): #reversed() looks at most recent received email first based on date
#while mainChoiceDisplay0 == True:
try:
###!!! uncomment this to see details about every email even if they aren't relevant to the search !!!####
print("Looking at", msg, msg.SentOn.date())
#msg.Sender is sender name, msg.SenderEmailAddress is the address.
if ( (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()) #show number of messages from relevant senders
for x in msg.Attachments:
if str(".pdf").casefold() in str(x): #casefold() checks upper or lower case combinations e.g PDF pdf Pdf
x.SaveAsFile("C:\\Users\\"+emailUser+"\\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()))
#break
#Stop searching inbox earlier than searchDate
nextMail() #Look at the next most recent email, repeat until end of date range
#Final message to indicate end of search, with subject line and date of the final message.
if msg.SentOn.date() < searchDate:
print("Reached end of date range: final message -", msg, msg.SentOn.date(), "\n")
mainChoiceDisplay0 = False
topMenu()
except UnicodeEncodeError: #unsupported characters
print("Subject line could not be parsed", msg, msg.SentOn.date())
#continue
except OverflowError: #caused by invalid or very old date e.g 01-01-0000
print("Date calculation caused an overflow error, possibly because you entered a very old date e.g 01-01-0000 at mainChoice0()")
#break
except AttributeError: #The email was the wrong class for the attribute msg.Sender or you tried to use an attribute that doesn't work for that method
print("Attribute error (not class 43 mail) for item at mainChoice0() - .MessageClass", msg.MessageClass, '- Class', msg.Class)
pass
except ValueError:
print("Invalid input caused a value error in mainChoice0().")

Instead of iterating over all items and checking separate property every time you need to use the Find/FindNext or Restrict methods of the Items class. Read more about thses methods in the following articles:
How To: Retrieve Outlook calendar items using Find and FindNext methods
How To: Use Restrict method in Outlook to get calendar items

Related

Python Gmail API. Search for emails based on Labels and Ascending Time Order?

I am using the Gmail API with Python. I would like to search for email messages using a label, in time ascending order. I am able to search for emails using a label or list of labels with the following function:
def gmailAPIMessageLabelSearch(self, labelList, userID="me", allPages=False):
try:
self.GLogger.info("Attempting to search emails with labelList (" + str(labelList)+ ") and userID (" +str(userID)+ ")")
service = self.gmailAPIService
if service is None:
logging.error('Gmail Service not initialized')
return False
response = service.users().messages().list(userId=userID, labelIds=labelList).execute()
messages = []
if 'messages' in response:
messages.extend(response['messages'])
if allPages is True:
while 'nextPageToken' in response:
page_token = response['nextPageToken']
response = service.users().messages().list(userId=userID, labelIds=labelList, pageToken=page_token).execute()
if 'messages' in response:
messages.extend(response['messages'])
self.GLogger.info("Successfully searched emails with labelList (" + str(labelList)+ ") and userID (" +str(userID)+ "). Number of matching emails (" +str(len(messages))+ ")")
return messages
except:
self.GLogger.error("An error was encounrtered while searching for messages with google API and label list")
tb = traceback.format_exc()
self.GLogger.exception(tb)
return False
However, I would specifically like to ensure that I am receiving emails in Time Ascending Order. So the search should return with the oldest first and newest last.
Also, I am not able to simply download the emails and then sort. The emails are used in real time, so they cannot be stored and then sorted. I am aware it would be possible to simply download the emails and then extract their time and sort accordingly. However, this is not an option. I would need the results of the search to already provide this list in ascending time order.
Is there a way of doing this?
I tried searching and checking the Gmail API for the list() function but I don't see a way. I tried applying "Oldest" label, and didn't do anything.
The docs say the the q parameter supports the same query format as the Gmail search box.
See here for which terms it accepts: https://support.google.com/mail/answer/7190?hl=en
Some of those terms can be useful to filter messages.
As Google mail does not provide the ability to invert the sorting, the easiest way is to retrieve the first page using list, the response then includes an estimate for the total number of results resultSizeEstimate, based on this you can jump to the last page and then you only have to sort the emails from that page.

How do I validate a user's input and make sure it is the correct type and within a given range?

I'm trying to write a program that will help me in my job to write behavioral reports on teenage boys in a therapeutic program. The goal is to make it easier to write the everyday expectations(eating, hygiene, school attendance, etc) so that I can write the unique behaviors and finish the report faster. I'm currently working on how many meals the boy at during the day and am trying to get it to validate that the user input is the correct type of input and within the correct range(0-3). Afterwards I want to change the variable depending on the answer given so it will print a string stating how many meals they ate. I've been able to get the program to validate the type to make sure that the user gave and integer but I cant get it to make sure that the given answer was in the desired range. Any help would be appreciated I only started learning python and programming a month ago so very inexperienced.
Here's what my code looks like so far and what I would like the variable to change to depending on the given answer.
Name=input('Student Name:')
while True:
try:
Meals=int(input('Number of Meals(between 0-3):'))
except ValueError:
print ('Sorry your response must be a value between 0-3')
continue
else:
break
while True:
if 0<= Meals <=3:
break
print('not an appropriate choice please select a number between 0-3')
#if Meals== 3:
#Meals= 'ate all 3 meals today'
#elif Meals==2:
#Meals='ate 2 meals today'
#elif Meals==1:
#Meals='ate only 1 meal today'
#elif Meals==0:
#Meals='did not eat any meals today'
You can check if number of inputted meals falls to valid range and if yes, break from the loop. If not, raise an Exception:
while True:
try:
meals = int(input('Number of Meals(between 0-3):'))
if 0 <= meals <= 3:
break
raise Exception()
except:
print('Please input valid integer (0-3)')
print('Your choice was:', meals)
Prints (for example):
Number of Meals(between 0-3):xxx
Please input valid integer (0-3)
Number of Meals(between 0-3):7
Please input valid integer (0-3)
Number of Meals(between 0-3):2
Your choice was: 2

How to fetch outlook mails with particular keyword in subject using win32com in Python?

I am trying to write a code which will print body of the mail if it came in past three days with a particular keyword in subject. For ex: 'approved' keyword here in my code. It should list contents of all the mails of past three days having approved as keyword in subject. Could you please suggest how to do that?
Here is my working code, only filtering is required using 'approved' keyword:
import win32com.client
import os
import time
import datetime as dt
from datetime import timedelta
# this is set to the current time
date_time = dt.datetime.now()
# this is set to three days ago
lastThreeDaysDateTime = dt.datetime.now() - dt.timedelta(days = 3)
outlook = win32com.client.Dispatch("Outlook.Application").GetNameSpace("MAPI")
inbox = outlook.GetDefaultFolder(6)
# retrieve all emails in the inbox, then sort them from most recently received to oldest (False will give you the reverse). Not strictly necessary, but good to know if order matters for your search
messages = inbox.Items
messages.Sort("[ReceivedTime]", True)
# restrict to messages from the past hour based on ReceivedTime using the dates defined above.
# lastHourMessages will contain only emails with a ReceivedTime later than an hour ago
# The way the datetime is formatted DOES matter; You can't add seconds here.
#lastHourMessages = messages.Restrict("[ReceivedTime] >= '" +lastHourDateTime.strftime('%m/%d/%Y %H:%M %p')+"'")
lastThreeDaysMessages = messages.Restrict("[ReceivedTime] >= '" +lastThreeDaysDateTime.strftime('%m/%d/%Y %H:%M %p')+"'")
#lastMinuteMessages = messages.Restrict("[ReceivedTime] >= '" +lastMinuteDateTime.strftime('%m/%d/%Y %H:%M %p')+"'")
print ("Current time: "+date_time.strftime('%m/%d/%Y %H:%M %p'))
print ("Messages from the past three days:")
#GetFirst/GetNext will also work, since the restricted message list is just a shortened version of your full inbox.
#print ("Using GetFirst/GetNext")
message = lastThreeDaysMessages.GetFirst()
while message:
#Here needs filter which should print only those mails having approved keyword
print (message.subject)
print (message.body)
message = lastThreeDaysMessages.GetNext()
You just need to combine both conditions into a single Restrict call:
lastThreeDaysMessages = messages.Restrict("[ReceivedTime] >= '" +lastThreeDaysDateTime.strftime('%m/%d/%Y %H:%M %p')+"' AND "urn:schemas:httpmail:subject" ci_phrasematch " & "'approved'")
Read more about that in the Creating Filters for the Find and Restrict Methods section in MSDN.
Here is an example of searching email with specific subject. I used python library called exchangelib for my project. Though my purpose/objective was different, but I believe exchangelib might be able to help you.
https://medium.com/#theamazingexposure/accessing-shared-mailbox-using-exchangelib-python-f020e71a96ab
Here is the python snippet where I am using exchangelib to look for the latest email with a specific subject:
for item in account.inbox.filter(subject__contains='Data is ready for').order_by('-datetime_received')[:1]:

Cannot pass entire for loop to email body in Python

First let me state that I am not a developer and have no formal training. I have a little experience with Python and have written scripts for telecom reporting. I'm working on a script that queries a database to determine the number of times a keypad was pressed. For example, key 1 was pressed 100 times, key 2 was pressed 300 times and so forth. I've been able to successfully retrieve the data and when I use the print function it works fine. However, when I attempt to take that same data and get it into the body of an email, it only seems to show the second index and not the first. Can someone help me understand why this happens and suggest how to correct? My for loop is indented, my email code is not. I've tried changing indenting and end up with two incorrect emails instead of one.
for row in data:
rowtolist = [elem for elem in row]
buttonkey = rowtolist[0]
timespressed = rowtolist[1]
data1 = 'Button {} was pressed {} times'.format(buttonkey,timespressed)
print(data1)
Output is:
Button 1 was pressed 710 times
Button 2 was pressed 1353 times
but when I attempt to take this data and add it to an email with this:
SERVER = "smtp.server"
FROM = "mysmtpserver"
TO = ["testemailaddress"]
SUBJECT = "Cisco Unity Report"
TEXT = data
message = """From: %s\r\nTo: %s\r\nSubject: %s\r\n\
%s\r\n
""" %(FROM, ",".join(TO), SUBJECT, TEXT)
server = smtplib.SMTP(SERVER)
server.sendmail(FROM, TO, message)
server.quit()
cursor.close()
I get this in my email body :
Button 2 was pressed 1353 times
I'd like to know why I cannot get the same info as if I am using the print function.
I presume data and data1 are same.
You need to store the result of each iteration as follows,
results = list()
for row in data:
rowtolist = [elem for elem in row]
buttonkey = rowtolist[0]
timespressed = rowtolist[1]
data = f'Button {buttonkey} was pressed {timespressed} times'
results.append(data)
Then you can attach the result to the mail body as follows,
TEXT = "\n".join(results)

How to print one error after going through invalid index index in list based on user input

Basically going through a list a printing related indexes to the list based on the mrp_number input.
I want to print an error statement or at least have it loop back to the user having to input the correct mrp_number again. My issue is that this is a large list with a good amount of rows with missing mrp_numbers. How do I loop through this list and have the error print once only?
I've done the else statement but it'll keep printing error as it reiterates through the list with a missing mrp_controller. I tried learning try and exception handling but I've no clue.
def mrp_assignment():
mrp_number = input("Enter MRP Controller Number ")
for row in polist:
purchasing = row[0]
material = row[7]
mrp_controller = row[8]
description = row[9]
if mrp_number in mrp_controller:
print(mrp_controller, purchasing, material, description)
else:
print('error')
I expected one error to be printed but I got multiple errors printed
Simply put a break after print('error'). This will break your for loop if error occurs.

Resources