I am trying to create rules to move emails from a long list of senders to specific folders. For example, if I receive an email from john#email.com, I want it to be moved from "Inbox" to "workstuff\John" (john is a subfolder of workstuff).
I'm using comtypes.clients and python to do this because I found a similar post ( Setting a property using win32com ), in which one of the answers uses comtypes.clients in python.
I am also using https://learn.microsoft.com/en-us/office/vba/outlook/how-to/rules/create-a-rule-to-move-specific-e-mails-to-a-folder as a guideline.
import comtypes.client
o = comtypes.client.CreateObject("Outlook.Application")
rules = o.Session.DefaultStore.GetRules()
rule = rules.Create("Test", 0)
condition = rule.Conditions
condition.From.Recipients.Add(str("fabracht"))
condition.From.Recipients.ResolveAll
#.From.Recipients("fabracht#gmail.com")
condition.Enabled = True
root_folder = o.GetNamespace('MAPI').Folders.Item(1)
dest_folder = root_folder.Folders["Evergreen1"].Folders["Chemistry"]
move = rule.Actions.MoveToFolder
move.__MoveOrCopyRuleAction__com__set_Enabled(True)
move.__MoveOrCopyRuleAction__com__set_Folder(dest_folder)
rules.Save()
I've been able to create the rule, which shows up in outlook. But the rule is missing the "from" part. Basically it says:
" Apply this rule after the message arrives
Move it to the john folder "
I expected the rule to be:
" Apply this rule after the message arrives
From john#email.com
Move it to the john folder "
The article mentioned in your post contains the following code for dealing with the From part:
'Specify the condition in a ToOrFromRuleCondition object
'Condition is if the message is from "Eugene Astafiev"
Set oFromCondition = oRule.Conditions.From
With oFromCondition
.Enabled = True
.Recipients.Add ("Eugene Astafiev")
.Recipients.ResolveAll
End With
The code should look like the following:
import comtypes.client
o = comtypes.client.CreateObject("Outlook.Application")
rules = o.Session.DefaultStore.GetRules()
rule = rules.Create("Test", 0)
condition = rule.Conditions
condition.From.Recipients.Add(str("fabracht"))
condition.From.Recipients.ResolveAll
oFromCondition = oRule.Conditions.From
oFromCondition.Enabled = True
oFromCondition.Recipients.Add("john#email.com")
oFromCondition.Recipients.ResolveAll
#.From.Recipients("fabracht#gmail.com")
condition.Enabled = True
root_folder = o.GetNamespace('MAPI').Folders.Item(1)
dest_folder = root_folder.Folders["Evergreen1"].Folders["Chemistry"]
move = rule.Actions.MoveToFolder
move.__MoveOrCopyRuleAction__com__set_Enabled(True)
move.__MoveOrCopyRuleAction__com__set_Folder(dest_folder)
rules.Save()
Related
I'm Jan and it's my first post here and the following code is also my first python code. So please don't judge me, if the code is not well shaped :) and don't wonder I had to reduce my mail body.
With the following code I try to generate several msg file depending on a user list called "customer_names". The idea is to iterate through this list and to adjust the email body espacially the placeholder for "Customer". The rest of the body content is not so important. Everything works good except the iteration through the list. I have a suggestion that I may need to increment the index for the list in the loop. Do you have any ideas.
import win32com.client as win32
import datetime
import random
# List of customer names
customer_names = ['Name1','Name2','Name3']
current_customer_index = 0
# List of email providers
email_providers = ['provider1', 'provider2', 'provider3']
# set up the Outlook application
outlook = win32.Dispatch('outlook.application')
# create a new email
mail = outlook.CreateItem(0)
# set the subject and recipients
mail.Subject = "Request for Customer Information"
mail.To = "user#emailprovider.net"
# Message body for the email
message = f"Dear User,\n\nThe information we require from [Customer] is as follows:\n\n- Email Address: [Email Address] \n\nWe kindly request that you send the requested information to us within [number of days] days. \n\n Kind regards."
# set the number of days for the customer to respond
num_days = 7
# set the importance of the email to normal
mail.Importance = 1 # 0=Low, 1=Normal, 2=High
# set the sensitivity of the email to normal
mail.Sensitivity = 0 # 0=Normal, 1=Personal, 2=Private, 3=Confidential
# set the read receipt option to true
mail.ReadReceiptRequested = True
# add a reminder for the sender to follow up in 3 days
mail.FlagRequest = "Follow up"
mail.FlagDueBy = (datetime.date.today() + datetime.timedelta(days=3)).strftime('%m/%d/%Y')
# Generate a random email
for i in range(len(customer_names)):
customer = customer_names[i]
message_with_data = message.replace("[Customer]", customer)
message_with_data = message_with_data.replace("[number of days]", str(num_days))
mail.Body = message_with_data
file_name = "Request_" + customer + ".msg"
file_path = "C:/Users/user/Desktop/Email/" + file_name
mail.SaveAs(file_path)
I found a way to query all global address in outlook with python,
import win32com.client
import csv
from datetime import datetime
# Outlook
outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries
# Create a dateID
date_id = (datetime.today()).strftime('%Y%m%d')
# Create empty list to store results
data_set = list()
# Iterate through Outlook address entries
for entry in entries:
if entry.Type == "EX":
user = entry.GetExchangeUser()
if user is not None:
if len(user.FirstName) > 0 and len(user.LastName) > 0:
row = list()
row.append(date_id)
row.append(user.Name)
row.append(user.FirstName)
row.append(user.LastName)
row.append(user.JobTitle)
row.append(user.City)
row.append(user.PrimarySmtpAddress)
try:
row.append(entry.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3a26001e"))
except:
row.append('None')
# Store the user details in data_set
data_set.append(row)
# Print out the result to a csv with headers
with open(date_id + 'outlookGALresults.csv', 'w', newline='', encoding='utf-8') as csv_file:
headers = ['DateID', 'DisplayName', 'FirstName', 'LastName', 'JobTitle', 'City', 'PrimarySmtp', 'Country']
wr = csv.writer(csv_file, delimiter=',')
wr.writerow(headers)
for line in data_set:
wr.writerow(line)
But it querys user one by one and it's very slow. I only need to query user from IT department out of 100,000 users. How can I write the filter to avoid querying all users?
On the Extended MAPI (C++ or Delphi) level, you can access the entries as IMAPITable MAPI interface and retrieve multiple entries in a single call (IMAPITable::QueryRows). You should still keep in mind that you are limited to 32KB on each request, so you'd need to retrieve the data in batches, which is still better than one entry at a time.
IMAPITable is also exposed as AddressEntries.RawTable property in OOM, but since it is Extended MAPI, it can be accessed in C++ or Delphi only.
If using Redemption (any language, I am its author) is an option, it exposes its own MAPITable interface, and in particular ExecSQL method, which allows to retrieve data in a single call.
In VBA:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set GAL = Session.AddressBook.GAL
set Table = GAL.AddressEntries.MAPITable
set Recordset = Table.ExecSQL("SELECT Name, FirstName, LastName, JobTitle, EntryID, " & _
"""http://schemas.microsoft.com/mapi/proptag/0x3A27001f"" As BusinessAddressCity, " & _
"""http://schemas.microsoft.com/mapi/proptag/0x3a26001f"" As BusinessAddressCountry, " & _
"""http://schemas.microsoft.com/mapi/proptag/0x39FE001F"" As PrimarySmtpAddress, " & _
"from list ")
while not Recordset.EOF
Debug.Print(Recordset.Fields("Name").Value & " - " & Recordset.Fields("PrimarySmtpAddress").Value)
Recordset.MoveNext
wend
You mention that you prefer to query the data rather than retrieve all entries - that should be the preferred solution; you should never retrieve all data unless you are creating some kind of synchronization app and you need everything on initial sync. Unfortunately, address book interfaces in MAPI (and hence in Outlook Object Model) are not very flexible when it comes to searching, especially compared to the search capabilities for the messages in folders.
If you want a single matching GAL user, use Application.Session.CreateRecipient / Recipient.Resolve and hope the alias resolves and there are no ambiguous entries. the search will be performed in all address book containers on the search path.
If you want multiple entries, you can filter on a few properties explicitly exposed by GAL for searching (Alias, City, Company, Department, First Name, Last Name, Office, Title). This is what you see in the address book window in Outlook if you click "Advanced Find". Redemption exposes it through the RDOAddressListSearch object:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set AddrList = Session.Addressbook.GAL
set Search = AddrList.Search
Search.FirstName = "John"
Search.City = "Phoenix"
set AddressEntries = Search.GetResults
for each AddressEntry in AddressEntries
Debug.Print AddressEntry.Name
next
#Dmitry Streblechenko 's method solved my problem.
The final code that can work is:
Set Session = CreateObject("Redemption.RDOSession")
Session.Logon
Set GAL = Session.AddressBook.GAL
Set AddrList = Session.AddressBook.GAL
Set Search = AddrList.Search
Search.DisplayName = "renlei"
Set AddressEntries = Search.GetResults
For Each AddressEntry In AddressEntries
Debug.Print AddressEntry.Name
Next
Every morning I get spot data on FX volumes via an email, I'd like to build a process to search two pieces of data within the body of the email and save them as a new variable which I can then refer to later.
I've got the process to search my emails, order them according to date and check whether the entered data exists within the emails, but because the data is contained within a format between two commas, I am unsure how to take that data out and assign it to a new variable.
Format for example is this:
BWP/USD,0
CHF/AMD T,0
This is what I've achieved thus far:
import win32com.client
import os
import time
import re
# change the ticker to the one you're looking for
FX_volume1 = "BWP/USD"
FX_volume2 = "CHF/AMD"
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
messages = inbox.Items
messages.Sort("[ReceivedTime]", True)
# find spot data
for message in messages:
if message.subject.startswith("FX SPOT FIGURES"):
if FX_volume1 and FX_volume2 in message.body:
data = message.body
print(data)
else:
print('No data for', FX_volume1, 'or', FX_volume2, 'was found')
break
Any idea how to take this forward?
Thanks for any assistance/pointers
import win32com.client
import os
import time
import re
# change the ticker to the one you're looking for
FX_volume1 = "BWP/USD"
FX_volume2 = "CHF/AMD"
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
messages = inbox.Items
messages.Sort("[ReceivedTime]", True)
# find spot data
for message in messages:
if message.subject.startswith("FX SPOT FIGURES"):
case1 = re.match(FX_volume1 + ",(\d*)", message.body)
case2 = re.match(FX_volume2 + ",(\d*)", message.body)
case (1 and 2) will be match objects if a match is found, else they will be None. To retrieve your values just do val = case1.group(1). Hence:
EDIT:
if case1 not None:
FX_vol1_val = case1.group(1)
if case2 not None:
FX_vol2_val = case1.group(1)
For more info on match objects:
https://docs.python.org/3/library/re.html#match-objects
If you are expecting floats, see the following link:
Regular expression for floating point numbers
EDIT 2:
Hi, so as you couldn't get it working I gave it a quick try and it worked for me with the following example. Just to add to regex notation, anything that you put in brackets (), if the pattern matches, the contents between the brackets will be stored.
import re
my_text = "BWP/USD,1"
FX_pattern = "BWP/USD," # added the comma here for my convinience
my_match = re.match(FX_pattern, "(\d*)")
print("Group 0:", my_match.group(0))
print("Group 1:", my_match.group(1))
Printout:
Group 0: BWP/USD,1
Group 1: 1
TLDR; I am running an Algorand PrivateNet in a sandbox. I would like to know how I can use the Python SDK for Algorand to return the address and balance of the accounts on this network.
Longer version;
Until now I have been using "./sandbox goal account list" to find the account addresses and account balances in Bash, this returns:
[offline]
I3CMDHG236HVBDMCKEM22DLY5L2OJ3CBRUDHG4PVVFGEZ4QQR3X3KNHRMU
I3CMDHG236HVBDMCKEM22DLY5L2OJ3CBRUDHG4PVVFGEZ4QQR3X3KNHRMU
8012889074131520 microAlgos
[offline]
3NSWKJTYMW3PSZZDIE6NCHMRTOZ24VY5G3OJNDSTYRVRXMZBZVPCGQHJJI
3NSWKJTYMW3PSZZDIE6NCHMRTOZ24VY5G3OJNDSTYRVRXMZBZVPCGQHJJI
1001611000000000 microAlgos
[online]
5KLSI3AHMBBDALXBEO2HEA3PBBCBAYT4PIHCD3B25557WGWUZGRTQETPHQ
5KLSI3AHMBBDALXBEO2HEA3PBBCBAYT4PIHCD3B25557WGWUZGRTQETPHQ
100000 microAlgos
I have tried to utilise the algod and v2client.indexer modules as outlined below but instead of the 3 accounts listed above 4 are being listed by my code (the original 3 +1). The code:
#imports
from algosdk import algod, v2client
# instatiate AlgodClient
def connect_to_network():
algod_address = "http://localhost:4001"
algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
algod_client = algod.AlgodClient(algod_token, algod_address)
return algod_client
# instantiate IndexClient
def indexer():
indexer_token = ""
indexer_address = "http://localhost:8980"
indexer_client = v2client.indexer.IndexerClient(indexer_token, indexer_address)
return indexer_client
# returns all account details from indexer
def account_details():
raw_details = indexer().accounts() # <-- I think the error is here because this returns 4 addresses.
account_details = []
for details in raw_details["accounts"]:
account_details.append({"address":details["address"], "amount": details["amount"]})
return account_details
# returns the amount in each account
def account_amounts(account_address):
return connect_to_network().account_info(account_address)
# returns account address and amount for each account
for detail in account_details():
print(account_amounts(detail["address"])["address"])
print(account_amounts(detail["address"])["amount"])
This returns:
I3CMDHG236HVBDMCKEM22DLY5L2OJ3CBRUDHG4PVVFGEZ4QQR3X3KNHRMU
8013089091731545
NTMTJYBQHWUXEGVG3XRRX5CH6FCYJ3HKCSIOYW4DLAOF6V2WHSIIFMWMPY
1001636000000000
3NSWKJTYMW3PSZZDIE6NCHMRTOZ24VY5G3OJNDSTYRVRXMZBZVPCGQHJJI
1001636000000000
5KLSI3AHMBBDALXBEO2HEA3PBBCBAYT4PIHCD3B25557WGWUZGRTQETPHQ
100000
How can I replicate "./sandbox goal account list" with py-algorand-sdk?
(Bonus question) what is this extra mystery "account" that appears when I use the SDK compared to Bash?
The issue above arose because like with ./sandbox goal accounts listI was trying to return the accounts I had keys for locally in the KMD but my call to the indexer raw_details = indexer().accounts() was returning all accounts on the network.
To remedy this, instead of importing and using the v2client.indexer module import the wallet module from the algosdk library. These are the necessary imports:
from algosdk import algod, kmd, wallet
Then set up the connections to algod, kmd, and your wallet:
# instatiate AlgodClient
def connect_to_network():
algod_address = "http://localhost:4001"
algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
algod_client = algod.AlgodClient(algod_token, algod_address)
return algod_client
# instantiate KMDClient <--- this is used as a parameter when instantiating the Wallet Class.
def key_management():
kmd_address = "http://localhost:4002"
kmd_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
kmd_client = kmd.KMDClient(kmd_token, kmd_address)
return kmd_client
#get wallet details
def wallet_details():
wallet_name = 'unencrypted-default-wallet'
password = ""
kmd_client = key_management()
return wallet.Wallet(wallet_name, password, kmd_client)
You can now return the account addresses held locally in the KMD:
keys = wallet_details().list_keys()
Now that you have the addresses you can query use the algod client to return the amount of (Micro) Algo held at each of these addresses.
I try to code a Find & Replace method with Python in LibreOffice's Calc to replace all the ".+" with "&" (in a single column - not so important) - unfortunately, even a standard Find & Replace method seems to be impossible (to me). That's what I have up to now:
import uno
def search()
desktop = XSCRIPTCONTEXT.getDesktop()
document = XSCRIPTCONTEXT.getDocument()
ctx = uno.getComponentContext()
sm = ctx.ServiceManager
dispatcher = sm.createInstanceWithContext("com.sun.star.frame.DispatchHelper", ctx)
model = desktop.getCurrentComponent()
doc = model.getCurrentController()
sheet = model.Sheets.getByIndex(0)
replace = sheet.createReplaceDescriptor()
replace.SearchRegularExpression = True
replace.SearchString = ".+$"
replace.ReplaceString ="&"
return None
And what happens: totally nothing! I will be happy and thankful for every hint, sample code and motivating words!
This code changes all non-empty cells in column A to &:
def calc_search_and_replace():
desktop = XSCRIPTCONTEXT.getDesktop()
model = desktop.getCurrentComponent()
sheet = model.Sheets.getByIndex(0)
COLUMN_A = 0
cellRange = sheet.getCellRangeByPosition(COLUMN_A, 0, COLUMN_A, 65536);
replace = cellRange.createReplaceDescriptor()
replace.SearchRegularExpression = True
replace.SearchString = r".+$"
replace.ReplaceString = r"\&"
cellRange.replaceAll(replace)
Notice that the code calls replaceAll to actually do something. Also, from the User Guide:
& will insert the same string found with the Search RegExp.
So the replace string needs to be literal -- \&.