Skip to content

Commit 2b8dd40

Browse files
Merge pull request #9 from loadsmart/reply
Reply a message
2 parents d86649a + 1a40586 commit 2b8dd40

File tree

6 files changed

+138
-10
lines changed

6 files changed

+138
-10
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ message = client.send(
104104
print(message) # Gmail message: ABC123
105105
```
106106

107+
- Reply message
108+
109+
```python
110+
message_id = "..."
111+
message = client.get_message(message_id)
112+
113+
reply = '''
114+
I am out for vacation, will return <strong>Jan 14</strong>.
115+
116+
If it is really important, you can call me, but think twice.
117+
'''
118+
response = message.reply(reply)
119+
```
120+
107121
- Handle exceptions
108122

109123
Exceptions are part of every developer day-to-day. You may want to handle exceptions as follows:

gmail_wrapper/client.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,29 +131,71 @@ def get_attachment_body(self, id, message_id):
131131

132132
return AttachmentBody(raw_attachment_body)
133133

134-
def _make_sendable_message(self, subject, html_content, to, cc, bcc):
134+
def _make_sendable_message(
135+
self,
136+
subject,
137+
html_content,
138+
to,
139+
cc,
140+
bcc,
141+
references,
142+
in_reply_to,
143+
thread_id=None,
144+
):
135145
message = MIMEText(html_content, "html")
136146
message["subject"] = subject
137147
message["from"] = self.email
138148
message["to"] = to
139149
message["cc"] = ",".join(cc)
140150
message["bcc"] = ",".join(bcc)
151+
message["references"] = " ".join(references)
152+
message["in-reply-to"] = " ".join(in_reply_to)
141153
return {
142154
"raw": base64.urlsafe_b64encode(bytes(message.as_string(), "utf-8")).decode(
143155
"utf-8"
144-
)
156+
),
157+
"threadId": thread_id,
145158
}
146159

147-
def send_raw(self, subject, html_content, to, cc=None, bcc=None):
160+
def send_raw(
161+
self,
162+
subject,
163+
html_content,
164+
to,
165+
cc=None,
166+
bcc=None,
167+
references=None,
168+
in_reply_to=None,
169+
thread_id=None,
170+
):
148171
sendable = self._make_sendable_message(
149-
subject, html_content, to, cc if cc else [], bcc if bcc else []
172+
subject,
173+
html_content,
174+
to,
175+
cc if cc else [],
176+
bcc if bcc else [],
177+
references if references else [],
178+
in_reply_to if in_reply_to else [],
179+
thread_id,
150180
)
151181

152182
return self._execute(
153183
self._messages_resource().send(userId=self.email, body=sendable)
154184
)
155185

156-
def send(self, subject, html_content, to, cc=None, bcc=None):
157-
raw_sent_message = self.send_raw(subject, html_content, to, cc, bcc)
186+
def send(
187+
self,
188+
subject,
189+
html_content,
190+
to,
191+
cc=None,
192+
bcc=None,
193+
references=None,
194+
in_reply_to=None,
195+
thread_id=None,
196+
):
197+
raw_sent_message = self.send_raw(
198+
subject, html_content, to, cc, bcc, references, in_reply_to, thread_id
199+
)
158200

159201
return Message(self, raw_sent_message)

gmail_wrapper/entities.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ def id(self):
8181
def subject(self):
8282
return self.headers.get("Subject")
8383

84+
@property
85+
def from_address(self):
86+
return self.headers.get("From")
87+
88+
@property
89+
def message_id(self):
90+
"""
91+
While self.id is the user-bound id of the message, self.message_id
92+
is the global id of the message, valid for every user on the thread.
93+
"""
94+
return self.headers.get("Message-ID")
95+
96+
@property
97+
def thread_id(self):
98+
if "threadId" not in self._raw:
99+
self._raw = self._client.get_raw_message(self.id)
100+
101+
return self._raw.get("threadId")
102+
84103
@property
85104
def date(self):
86105
ms_in_seconds = 1000
@@ -101,5 +120,15 @@ def modify(self, add_labels=None, remove_labels=None):
101120
self.id, add_labels=add_labels, remove_labels=remove_labels
102121
)
103122

123+
def reply(self, html_content):
124+
return self._client.send(
125+
subject=f"Re:{self.subject}",
126+
html_content=html_content,
127+
to=self.from_address,
128+
references=[self.message_id],
129+
in_reply_to=[self.message_id],
130+
thread_id=self.thread_id,
131+
)
132+
104133
def __str__(self):
105134
return "Gmail message: {}".format(self.id)

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def raw_complete_message():
4747
{"name": "To", "value": "[email protected]"},
4848
{"name": "From", "value": "[email protected]"},
4949
{"name": "Subject", "value": "Urgent errand"},
50+
{"name": "Message-ID", "value": "<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"},
5051
],
5152
"parts": [
5253
{

tests/test_client.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ def test_it_creates_a_proper_sendable_message(self, client):
234234
235235
236236
bcc = []
237-
sendable = client._make_sendable_message(subject, content, to, cc, bcc)
237+
references = []
238+
in_reply_to = []
239+
sendable = client._make_sendable_message(
240+
subject, content, to, cc, bcc, references, in_reply_to
241+
)
238242
decoded = base64.urlsafe_b64decode(sendable["raw"]).decode("utf-8")
239243
assert decoded.startswith("Content-Type: text/html;")
240244
assert f"subject: {subject}\n" in decoded
@@ -257,8 +261,9 @@ def test_it_send_and_return_a_raw_message(self, mocker, raw_complete_message):
257261
258262
body={
259263
"raw": base64.urlsafe_b64encode(
260-
b'Content-Type: text/html; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nsubject: Hi there!\nfrom: [email protected]\nto: [email protected]\ncc: \nbcc: \n\n<html><p>Hey</p></html>'
261-
).decode("utf-8")
264+
b'Content-Type: text/html; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nsubject: Hi there!\nfrom: [email protected]\nto: [email protected]\ncc: \nbcc: \nreferences: \nin-reply-to: \n\n<html><p>Hey</p></html>'
265+
).decode("utf-8"),
266+
"threadId": None,
262267
},
263268
)
264269

@@ -275,7 +280,43 @@ def test_it_returns_the_sent_message(self, client, mocker, raw_complete_message)
275280
276281
)
277282
mocked_send_raw_message.assert_called_once_with(
278-
"Hi there!", "<html><p>Hey</p></html>", "[email protected]", None, None
283+
"Hi there!",
284+
"<html><p>Hey</p></html>",
285+
286+
None,
287+
None,
288+
None,
289+
None,
290+
None,
279291
)
280292
assert isinstance(sent_message, Message)
281293
assert sent_message.id == raw_complete_message["id"]
294+
295+
296+
class TestReply:
297+
def test_it_returns_the_sent_message(self, client, mocker, raw_complete_message):
298+
message_to_reply = Message(client, raw_complete_message)
299+
expected_message_to_be_sent = {"id": "114ADC", "internalDate": "1566398665"}
300+
mocked_send_raw_message = mocker.patch(
301+
"gmail_wrapper.client.GmailClient.send_raw",
302+
return_value=expected_message_to_be_sent,
303+
)
304+
sent_message = message_to_reply.reply(
305+
"The quick brown fox jumps over the lazy dog"
306+
)
307+
mocked_send_raw_message.assert_called_once_with(
308+
"Re:Urgent errand",
309+
"The quick brown fox jumps over the lazy dog",
310+
311+
None,
312+
None,
313+
[
314+
"<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"
315+
],
316+
[
317+
"<BY5PR15MB353717D866FC27FEE4DB4EC7F77E0@BY5PR15MB3537.namprd15.prod.outlook.com>"
318+
],
319+
"AA121212",
320+
)
321+
assert isinstance(sent_message, Message)
322+
assert sent_message.id == expected_message_to_be_sent["id"]

tests/test_entities.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def test_it_fetch_additional_information_when_needed(
2525
return_value=raw_complete_message,
2626
)
2727
incomplete_message = Message(client, raw_incomplete_message)
28+
assert incomplete_message.thread_id == raw_complete_message["threadId"]
2829
assert (
2930
incomplete_message.subject
3031
== raw_complete_message["payload"]["headers"][2]["value"]

0 commit comments

Comments
 (0)