How to create a draft message in existing thread with gmail API - python

Using the python google client I able to create new messages within an existing thread with id threadId as follows:
message = (service.users().messages().send(userId='me', body={'threadId': <threadId>}, media_body=message['media_body'])
.execute())
(here I'm using media_body as it supports the /upload endpoint for large attachments)
This works fine for messages, and the threadId optional parameter is documented at https://developers.google.com/gmail/api/v1/reference/users/messages/send
However I have been unsuccessful doing the same when creating new draft messages, and don't see any docs about it at https://developers.google.com/gmail/api/v1/reference/users/drafts/create
I tried adding threadId to the draft body when doing draft = service.users().drafts().create(userId=user_id, body={'threadId': <threadId>}, media_body=message_body['media_body']).execute() at the create draft stage and it simply is ignored.
I also tried adding threadId to the body at the send draft stage: message = service.users().drafts().send( userId='me', body={'id': draft_id, threadId': <threadId>}).execute() and this was also ignored.
Either way the draft message just gets created in its own, new thread.
How can I create a new draft message within an existing thread with the gmail API (and specifically with Python client)?

email = self.service.users().messages().get(userId='me', id='1756f9403f66ac1d').execute() # sub in your message id
def get_field(self, email, field_name):
header = email['payload']['headers']
for m in header:
if m['name'] == field_name:
return m['value']
def add_draft(self, email, body):
message = MIMEText(body)
message['to'] = 'to#email.com'
message['from'] = 'from#email.com'
message['subject'] = self.get_field(email, 'Subject')
message['In-Reply-To'] = self.get_field(email, 'Message-ID')
message['References'] = self.get_field(email, 'Message-ID')# + ',' + self.get_field_a(email, 'References')
email_body = {'message' : {'threadId' : email['threadId'], 'raw' : base64.urlsafe_b64encode(message.as_string().encode('utf-8')).decode()}}
draft = self.service.users().drafts().create(userId='me', body=email_body).execute()
In this above example I was adding a draft as a response to a brand new thread that had one message sent to me. That particular email didn't have a value 'References' in the header so I commented out the section where I was extracting that field from the received email and adding it to the draft. For adding a draft to longer threads I believe you'll need to read and append this field. A function to safely append the field is left as an exercise to the reader :)
A great resource for seeing examples of what you should be sending is the GMail 'show original'. Open an email -> three buttons -> Show Original. This opens a page where you can see the values of the actual fields in the emails gmail is sending for you when you (the human you) sends emails. For example, here is the reference and in reply to section of the second message in a different thread:
References: <1243993195.RE.1603730339394.ref#mail.yahoo.com>,<1243993195.REDACTED.1603730339394#mail.yahoo.com>
In-Reply-To: <1243993195.REDACTED.1603730339394#mail.yahoo.com>
In this case, 'In-Reply-To' was set as the 'Message-Id' field of the prior message, and 'References' was set as prior 'Message-Id' plus the prior 'References' fields
Here's a screenshot of the final result:

As per documentation, a draft can be added to a thread as part of creating, updating, or sending a draft message.
In order to be part of a thread, a message or draft must meet the following criteria:
The requested threadId must be specified on the Message or
Draft.Message you supply with your request.
The References and In-Reply-To headers must be set in compliance
with the RFC 2822 standard.
The Subject headers must match.
The documentation also states that in both examples for creating a draft and sending a message, you would simply add a threadId key paired with a thread ID to a message's metadata, the message object.

Related

Pulling historical channel messages python

I am attempting to create a small dataset by pulling messages/responses from a slack channel I am a part of. I would like to use python to pull the data from the channel however I am having trouble figuring out my api key. I have created an app on slack but I am not sure how to find my api key. I see my client secret, signing secret, and verification token but can't find my api key
Here is a basic example of what I believe I am trying to accomplish:
import slack
sc = slack.SlackClient("api key")
sc.api_call(
"channels.history",
channel="C0XXXXXX"
)
I am willing to just download the data manually if that is possible as well. Any help is greatly appreciated.
messages
See below for is an example code on how to pull messages from a channel in Python.
It uses the official Python Slack library and calls
conversations_history with paging. It will therefore work with
any type of channel and can fetch large amounts of messages if
needed.
The result will be written to a file as JSON array.
You can specify channel and max message to be retrieved
threads
Note that the conversations.history endpoint will not return thread messages. Those have to be retrieved additionaly with one call to conversations.replies for every thread you want to retrieve messages for.
Threads can be identified in the messages for each channel by checking for the threads_ts property in the message. If it exists there is a thread attached to it. See this page for more details on how threads work.
IDs
This script will not replace IDs with names though. If you need that here are some pointers how to implement it:
You need to replace IDs for users, channels, bots, usergroups (if on a paid plan)
You can fetch the lists for users, channels and usergroups from the API with users_list, conversations_list and usergroups_list respectively, bots need to be fetched one by one with bots_info (if needed)
IDs occur in many places in messages:
user top level property
bot_id top level property
as link in any property that allows text, e.g. <#U12345678> for users or <#C1234567> for channels. Those can occur in the top level text property, but also in attachments and blocks.
Example code
import os
import slack
import json
from time import sleep
CHANNEL = "C12345678"
MESSAGES_PER_PAGE = 200
MAX_MESSAGES = 1000
# init web client
client = slack.WebClient(token=os.environ['SLACK_TOKEN'])
# get first page
page = 1
print("Retrieving page {}".format(page))
response = client.conversations_history(
channel=CHANNEL,
limit=MESSAGES_PER_PAGE,
)
assert response["ok"]
messages_all = response['messages']
# get additional pages if below max message and if they are any
while len(messages_all) + MESSAGES_PER_PAGE <= MAX_MESSAGES and response['has_more']:
page += 1
print("Retrieving page {}".format(page))
sleep(1) # need to wait 1 sec before next call due to rate limits
response = client.conversations_history(
channel=CHANNEL,
limit=MESSAGES_PER_PAGE,
cursor=response['response_metadata']['next_cursor']
)
assert response["ok"]
messages = response['messages']
messages_all = messages_all + messages
print(
"Fetched a total of {} messages from channel {}".format(
len(messages_all),
CHANNEL
))
# write the result to a file
with open('messages.json', 'w', encoding='utf-8') as f:
json.dump(
messages_all,
f,
sort_keys=True,
indent=4,
ensure_ascii=False
)
This is using the slack webapi. You would need to install requests package. This should grab all the messages in channel. You need a token which can be grabbed from apps management page. And you can use the getChannels() function. Once you grab all the messages you will need to see who wrote what message you need to do id matching(map ids to usernames) you can use getUsers() functions. Follow this https://api.slack.com/custom-integrations/legacy-tokens to generate a legacy-token if you do not want to use a token from your app.
def getMessages(token, channelId):
print("Getting Messages")
# this function get all the messages from the slack team-search channel
# it will only get all the messages from the team-search channel
slack_url = "https://slack.com/api/conversations.history?token=" + token + "&channel=" + channelId
messages = requests.get(slack_url).json()
return messages
def getChannels(token):
'''
function returns an object containing a object containing all the
channels in a given workspace
'''
channelsURL = "https://slack.com/api/conversations.list?token=%s" % token
channelList = requests.get(channelsURL).json()["channels"] # an array of channels
channels = {}
# putting the channels and their ids into a dictonary
for channel in channelList:
channels[channel["name"]] = channel["id"]
return {"channels": channels}
def getUsers(token):
# this function get a list of users in workplace including bots
users = []
channelsURL = "https://slack.com/api/users.list?token=%s&pretty=1" % token
members = requests.get(channelsURL).json()["members"]
return members

Extract outlook email body and recipient email address using python

I am trying to extract outlook email body, recipient address, subject and received date.
I was able to extract subject and received date but unable to extract body and recipient address:
Below is my code for subject and received date:
outlook = win32com.client.Dispatch('Outlook.Application').GetNamespace('MAPI')
namespace = outlook.Session
recipient = namespace.CreateRecipient("abc#xyz.com")
inbox = outlook.GetSharedDefaultFolder(recipient, 6)
messages = inbox.Items
email_subject = []
email_date = []
email_date_time = []
for x in messages:
sub = x.Subject
received_date = x.senton.date()
received_date_time = str(x.ReceivedTime)
email_subject.append(sub)
email_date.append(received_date)
email_date_time.append(received_date_time)
For body i am trying:
for x in messages:
body = x.Body
print(body)
but this is not working and i am getting the error below:
Traceback (most recent call last):
File "<ipython-input-85-d79967933b99>", line 2, in <module>
sub = x.Body
File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\dynamic.py", line 516, in __getattr__
ret = self._oleobj_.Invoke(retEntry.dispid,0,invoke_type,1)
com_error: (-2147467259, 'Unspecified error', None, None)
I've just run similar code on my computer, in an inbox with 3,000+ items of mixed type (skype message notifications, calendar invites/notifications, emails, etc.) and I cannot replicate this error, even for items where not m.Body -- I thought that was a possible culprit, maybe a certain type of item doesn't have a body would throw an error -- but that appears to not be the case:
>>> for m in messages:
... if not m.body:
... print(m.Subject)
... print(m.Body)
...
Accepted: Tables discussion
Message Recall Failure: tables/ new data status
Message Recall Failure: A few issues with the data
You should probably add a print(m.Class) because I still think that maybe certain types of items do not have a Body property.
This thread suggests that there may be a user/security setting that prevents programmatic access to Outlook, so you might want to double-check on that (though I think if not allowed, none of your code would work -- still, worth looking in to!).
I have figured out the source of this error. We are running into issues with the comObjectModelGaurd. Our group policy recently changed to disallow programatic access to protected mailItem objects.
Modifying Outlook Users Trust settings or the registry will fix the problem.
Since I can't replicate the error, perhaps I can still help you debug and identify the source of the problem, from which we can probably come up with a good solution.
Use a function to get the item's body, and use try/except to identify which items are causing error.
def getBody(m):
s = ""
try:
s = m.Body
except COMError:
s = '\t'.join(('Error!', m.Class, m.senton.date(), m.Subject))
return s
for m in messages:
print(getBody(m))
I think that I found a solution that worked. For me it was an issue with permissions, but I made the registry edits in https://www.slipstick.com/developer/change-programmatic-access-options/ and it worked a treat.
EDIT: I think this worked by unblocking some lower level permissions that enabled an outside program to access the outlook client.
Recalled Emails would have no Body hence we can find that via the MessageClass and exclude that particular Class
for i in messages:
if email.MessageClass != "IPM.Outlook.Recall":

python mapi messages collection getfirst() returns second and not the first message

I'm working with Outlook, using mapi library and Python. I try to get the first message out of few similar messages( only subject is different). Somehow, I get second message only. Here is my code:
self.outlook = win32com.client.Dispatch("Outlook.Application")
self.mapi = self.outlook.GetNamespace("MAPI")
folderHandle = self.mapi.GetDefaultFolder(folder)
messages = folderHandle.Items
message = messages.GetFirst()
The subject of the message I get -is the subject of the second email in the list( the email are similar except the subject), instead of the first message subject.
Items collection is not sorted in any particular order until you explicitly call Items.Sort.

Reading page's messages with Python Facebook SDK

Basically i need to get all messages of a page using facebook SDK in python.
Following some tutorial i arrived to this point:
import facebook
def main():
cfg = {
"page_id" : "MY PAGE ID",
"access_token" : "LONG LIVE ACCESS TOKEN"
}
api = get_api(cfg)
msg = "Hre"
status = api.put_wall_post(msg) #used to post to wall message Hre
x = api.get_object('/'+str(MY PAGE ID)+"/conversations/") #Give actual conversations
def get_api(cfg):
graph = facebook.GraphAPI(cfg['access_token'])
resp = graph.get_object('me/accounts')
page_access_token = None
for page in resp['data']:
if page['id'] == cfg['page_id']:
page_access_token = page['access_token']
graph = facebook.GraphAPI(page_access_token)
return graph
if __name__ == "__main__":
main()
The first problem is that api.get_object('/'+str(MY PAGE ID)+"/conversations/")returns a dictionary containing many informations, but what i would like to see is the messages they sent to me, while for now it print the user id that sent to me a message.
The output look like the following:
{u'paging': {u'next': u'https://graph.facebook.com/v2.4/571499452991432/conversations?access_token=Token&limit=25&until=1441825848&__paging_token=enc_AdCqaKAP3e1NU9MGSsvSdzDPIIDtB2ZCe2hCYfk7ft5ZAjRhsuVEL7eFYOOCdQ8okvuhZA5iQWaYZBBbrZCRNW8uzWmgnKGl69KKt4catxZAvQYCus7gZDZD', u'previous': u'https://graph.facebook.com/v2.4/571499452991432/conversations?access_token=token&limit=25&since=1441825848&__paging_token=enc_AdCqaKAP3e1NU9MGSsvSdzDPIIDtB2ZCe2hCYfk7ft5ZAjRhsuVEL7eFYOOCdQ8okvuhZA5iQWaYZBBbrZCRNW8uzWmgnKGl69KKt4catxZAvQYCus7gZDZD&__previous=1'}, u'data': [{u'link': u'/communityticino/manager/messages/?mercurythreadid=user%3A1055476438&threadid=mid.1441825847634%3Af2e0247f54f5c4d222&folder=inbox', u'id': u't_mid.1441825847634:f2e0247f54f5c4d222', u'updated_time': u'2015-09-09T19:10:48+0000'}]}
which is basically paging and data.
Given this is there a way to read the conversation?
In order to get the messages content you need first to request the single messages in the conversation, accessible with the 'id' field in the dictionary you copied, result of
x = api.get_object('/'+str(MY PAGE ID)+"/conversations/") #Give actual conversations
you can request the messages in the conversation by calling
msg = api.get_object('/'+<message id>)
Here it gets tricky, because following the graph api documentation you should receive back a dictionary with ALL the possible fields, including the 'message' (content) field. The function however returns only the fields 'created_time' and 'id'.
Thanks to this other question Request fields in Python Facebook SDK I found that you can request for those fields by adding a dict with such fields specified in the arguments of the graph.get_object() function. As far as I know this is undocumented in the facebook sdk reference for python.
The correct code is
args = {'fields' : 'message'}
msg = api.get_object('/'+<message id>, **args)
Similar question: Read facebook messages using python sdk

SendGrid Sending Multiple Copies

I'm using SendGrid to send emails from my python-based Heroku app.I'm okay with it taking 10 or so minutes to get to my inbox, but I'm receiving three copies of the message and I can't figure out why. Here is the relevant code:
import sendgrid
from sendgrid import SendGridError, SendGridClientError, SendGridServerError
sg = sendgrid.SendGridClient('xxx#heroku.com', 'xxx')
message = sendgrid.Mail()
message.add_to('John Doe <xxx#xxx.com>')
message.set_subject('Example')
message.set_html('Body')
message.set_text('Body')
message.set_from('Dark Knight <xxx#xxx.com>')
message.add_attachment('image.jpg', './image.jpg')
status, msg = sg.send(message)
#app.route('/test2')
def test2():
sg.send(message)
return "sent"
When I go to the relevant route I get 'sent' returned and the email is sent, but again, it send three copies. I'm not sure why. Any help would be great.
Emails one and two:
status, msg = sg.send(message) would send two emails and then set status and msg to the response object.
Email three: after you load the route sg.send(message) sends the next email.
I suggest you to use sendgrid sendmail api to send email. its efficient, fast way to send emails.
You are calling sg.send(message) three times in your code.
It is called twice here: status, msg = sg.send(message) - this will send one mail for status and log it's response to that variable. It will then send again for msg and log it's response to that variable as well.
Then when the user hits the /test2 the function is called again, making it three messages in total.
Here's how you might change it to log responses but just send the one message out:
import sendgrid
from sendgrid import SendGridError, SendGridClientError, SendGridServerError
sg = sendgrid.SendGridClient('xxx#heroku.com', 'xxx')
def sendMessage(options):
message = sendgrid.Mail()
message.add_to('John Doe <xxx#xxx.com>')
message.set_subject('Example')
message.set_html('Body')
message.set_text('Body')
message.set_from('Dark Knight <xxx#xxx.com>')
message.add_attachment('image.jpg', './image.jpg')
// send the message and log the results to status
msg = sg.send(message)
return msg
#app.route('/test2')
def test2():
// send the message, pass any options like email address (not required)
status = sendMessage(options)
return status
I've added a new function above to send out the message and given it an optional options var, so you could use that to pass things to the message, like a different email address, or subject.

Categories

Resources