Skip to content

Commit

Permalink
Merge pull request #9 from loadsmart/reply
Browse files Browse the repository at this point in the history
Reply a message
  • Loading branch information
luizguilhermefr authored Nov 5, 2019
2 parents d86649a + 1a40586 commit 2b8dd40
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 10 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ message = client.send(
print(message) # Gmail message: ABC123
```

- Reply message

```python
message_id = "..."
message = client.get_message(message_id)

reply = '''
I am out for vacation, will return <strong>Jan 14</strong>.
If it is really important, you can call me, but think twice.
'''
response = message.reply(reply)
```

- Handle exceptions

Exceptions are part of every developer day-to-day. You may want to handle exceptions as follows:
Expand Down
54 changes: 48 additions & 6 deletions gmail_wrapper/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,29 +131,71 @@ def get_attachment_body(self, id, message_id):

return AttachmentBody(raw_attachment_body)

def _make_sendable_message(self, subject, html_content, to, cc, bcc):
def _make_sendable_message(
self,
subject,
html_content,
to,
cc,
bcc,
references,
in_reply_to,
thread_id=None,
):
message = MIMEText(html_content, "html")
message["subject"] = subject
message["from"] = self.email
message["to"] = to
message["cc"] = ",".join(cc)
message["bcc"] = ",".join(bcc)
message["references"] = " ".join(references)
message["in-reply-to"] = " ".join(in_reply_to)
return {
"raw": base64.urlsafe_b64encode(bytes(message.as_string(), "utf-8")).decode(
"utf-8"
)
),
"threadId": thread_id,
}

def send_raw(self, subject, html_content, to, cc=None, bcc=None):
def send_raw(
self,
subject,
html_content,
to,
cc=None,
bcc=None,
references=None,
in_reply_to=None,
thread_id=None,
):
sendable = self._make_sendable_message(
subject, html_content, to, cc if cc else [], bcc if bcc else []
subject,
html_content,
to,
cc if cc else [],
bcc if bcc else [],
references if references else [],
in_reply_to if in_reply_to else [],
thread_id,
)

return self._execute(
self._messages_resource().send(userId=self.email, body=sendable)
)

def send(self, subject, html_content, to, cc=None, bcc=None):
raw_sent_message = self.send_raw(subject, html_content, to, cc, bcc)
def send(
self,
subject,
html_content,
to,
cc=None,
bcc=None,
references=None,
in_reply_to=None,
thread_id=None,
):
raw_sent_message = self.send_raw(
subject, html_content, to, cc, bcc, references, in_reply_to, thread_id
)

return Message(self, raw_sent_message)
29 changes: 29 additions & 0 deletions gmail_wrapper/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@ def id(self):
def subject(self):
return self.headers.get("Subject")

@property
def from_address(self):
return self.headers.get("From")

@property
def message_id(self):
"""
While self.id is the user-bound id of the message, self.message_id
is the global id of the message, valid for every user on the thread.
"""
return self.headers.get("Message-ID")

@property
def thread_id(self):
if "threadId" not in self._raw:
self._raw = self._client.get_raw_message(self.id)

return self._raw.get("threadId")

@property
def date(self):
ms_in_seconds = 1000
Expand All @@ -101,5 +120,15 @@ def modify(self, add_labels=None, remove_labels=None):
self.id, add_labels=add_labels, remove_labels=remove_labels
)

def reply(self, html_content):
return self._client.send(
subject=f"Re:{self.subject}",
html_content=html_content,
to=self.from_address,
references=[self.message_id],
in_reply_to=[self.message_id],
thread_id=self.thread_id,
)

def __str__(self):
return "Gmail message: {}".format(self.id)
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def raw_complete_message():
{"name": "To", "value": "foo@loadsmart.com"},
{"name": "From", "value": "john@doe.com"},
{"name": "Subject", "value": "Urgent errand"},
{"name": "Message-ID", "value": "<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"},
],
"parts": [
{
Expand Down
49 changes: 45 additions & 4 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,11 @@ def test_it_creates_a_proper_sendable_message(self, client):
to = "bob.dylan@loadsmart.com"
cc = ["agostinho.carrara@loadsmart.com", "jon.maddog@loadsmart.com"]
bcc = []
sendable = client._make_sendable_message(subject, content, to, cc, bcc)
references = []
in_reply_to = []
sendable = client._make_sendable_message(
subject, content, to, cc, bcc, references, in_reply_to
)
decoded = base64.urlsafe_b64decode(sendable["raw"]).decode("utf-8")
assert decoded.startswith("Content-Type: text/html;")
assert f"subject: {subject}\n" in decoded
Expand All @@ -257,8 +261,9 @@ def test_it_send_and_return_a_raw_message(self, mocker, raw_complete_message):
userId="foo@bar.com",
body={
"raw": base64.urlsafe_b64encode(
b'Content-Type: text/html; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nsubject: Hi there!\nfrom: foo@bar.com\nto: john@doe.com\ncc: \nbcc: \n\n<html><p>Hey</p></html>'
).decode("utf-8")
b'Content-Type: text/html; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nsubject: Hi there!\nfrom: foo@bar.com\nto: john@doe.com\ncc: \nbcc: \nreferences: \nin-reply-to: \n\n<html><p>Hey</p></html>'
).decode("utf-8"),
"threadId": None,
},
)

Expand All @@ -275,7 +280,43 @@ def test_it_returns_the_sent_message(self, client, mocker, raw_complete_message)
to="foo@bar.com",
)
mocked_send_raw_message.assert_called_once_with(
"Hi there!", "<html><p>Hey</p></html>", "foo@bar.com", None, None
"Hi there!",
"<html><p>Hey</p></html>",
"foo@bar.com",
None,
None,
None,
None,
None,
)
assert isinstance(sent_message, Message)
assert sent_message.id == raw_complete_message["id"]


class TestReply:
def test_it_returns_the_sent_message(self, client, mocker, raw_complete_message):
message_to_reply = Message(client, raw_complete_message)
expected_message_to_be_sent = {"id": "114ADC", "internalDate": "1566398665"}
mocked_send_raw_message = mocker.patch(
"gmail_wrapper.client.GmailClient.send_raw",
return_value=expected_message_to_be_sent,
)
sent_message = message_to_reply.reply(
"The quick brown fox jumps over the lazy dog"
)
mocked_send_raw_message.assert_called_once_with(
"Re:Urgent errand",
"The quick brown fox jumps over the lazy dog",
"john@doe.com",
None,
None,
[
"<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"
],
[
"<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"
],
"AA121212",
)
assert isinstance(sent_message, Message)
assert sent_message.id == expected_message_to_be_sent["id"]
1 change: 1 addition & 0 deletions tests/test_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_it_fetch_additional_information_when_needed(
return_value=raw_complete_message,
)
incomplete_message = Message(client, raw_incomplete_message)
assert incomplete_message.thread_id == raw_complete_message["threadId"]
assert (
incomplete_message.subject
== raw_complete_message["payload"]["headers"][2]["value"]
Expand Down

0 comments on commit 2b8dd40

Please sign in to comment.