根据 mimeType 在哪里可以找到电子邮件正文
Where to find body of email depending of mimeType
我正在向 User.messages 端点发出请求。所有返回的对象(电子邮件)都有一个我很难理解的 mimeType 属性。
更具体地说,我希望能够根据 mimeType 提取电子邮件正文,因为我已经注意到根据 mimeType,正文将位于 body
属性 在 payload
中,或在 parts
数组中。可以返回哪些不同的 mimeType,我在哪里可以找到每种 mimeType 的电子邮件正文?
可以返回的MIME类型有很多,这里列举几种:
- text/plain: 邮件正文只有纯文本
- text/html: 消息体只在HTML
- multipart/alternative:将包含两个部分,它们可以互为替代,例如:
- text/plain 纯文本邮件正文部分
- text/html 部分用于 html
中的邮件正文
- multipart/mixed:将包含许多不相关的部分,可以是:
- multipart/alternative如上,或text/plain或text/html如上
- application/octet-stream,或其他应用程序/* 用于附件的应用程序特定 MIME 类型
- image/png ot other image/* 用于图像,可以嵌入消息中。
所有这些的权威参考是 RFC 2046 https://www.ietf.org/rfc/rfc2046.txt(您可能还想查看 2044 和 2045)
要回答您的问题,请构建消息树,然后查找:
- 第一个 text/plain 或 text/html 部分(在邮件正文或 multipart/mixed 中)
- 第一个 text/plain 或 text/html 里面的一个 multipart/alternative,它可能是 multipart 混合的一部分。
复杂消息的示例:
multipart/mixed
- multipart/alternative
- text/plain <- 纯文本邮件正文
- text/html <- HTML
中的邮件正文
- application/zip <- 一个 zip 文件附件
-
我认为,如果您将 payload
本身视为 part
,那将是有意义的。假设我发送的消息只有一个主题和一个纯消息文本:
From: emtholin@gmail.com
To: emtholin@gmail.com
Subject: Example Subject
This is the plain text message
这将导致以下经过解析的消息:
{
"id": "154ecb53c10b74d8",
"threadId": "154ecb53c10b74d8",
"labelIds": [
"INBOX",
"SENT"
],
"snippet": "This is the plain text message",
"historyId": "38877",
"internalDate": "1464260181000",
"payload": {
"partId": "",
"mimeType": "text/plain",
"filename": "",
"headers": [
...
],
"body": {
"size": 31,
"data": "VGhpcyBpcyB0aGUgcGxhaW4gdGV4dCBtZXNzYWdlCg=="
}
},
"sizeEstimate": 355
}
如果我发送的消息包含纯文本部分、html 部分和图像,解析后将如下所示:
{
"id": "154ed5ccaa12f3df",
"threadId": "154ed5ccaa12f3df",
"labelIds": [
"SENT",
"INBOX",
"IMPORTANT"
],
"snippet": "This is a plain/html message with an image.",
"historyId": "841379",
"internalDate": "1464271162000",
"payload": {
"mimeType": "multipart/mixed",
"filename": "",
"headers": [
...
],
"body": {
"size": 0
},
"parts": [
{
"mimeType": "multipart/alternative",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "multipart/alternative; boundary=089e0122896c7c80d80533bf3205"
}
],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
}
],
"body": {
"size": 47,
"data": "VGhpcyBpcyBhIHBsYWluL2h0bWwgKm1lc3NhZ2UqIHdpdGggYW4gaW1hZ2UuDQo="
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/html; charset=UTF-8"
}
],
"body": {
"size": 73,
"data": "PGRpdiBkaXI9Imx0ciI-VGhpcyBpcyBhIHBsYWluL2h0bWwgPGI-bWVzc2FnZTwvYj4gd2l0aCBhbiBpbWFnZS48L2Rpdj4NCg=="
}
}
]
},
{
"partId": "1",
"mimeType": "image/png",
"filename": "smile.png",
"headers": [
...
],
"body": {
"attachmentId": "ANGjdJ-OrSy7VAYL-UbRyNtmySbZLlV-fV43zJF0_neNGZ8yKugsZAxb32eSb-CrbYIhF9NvjGwBVEjSkRrUWoCS7aDpgoQnt9WR7f2sa17qVEyOg_JVSbrGrunirvQw2dY-SxxB3Y0JP3aYDHSBXpNO6fFCByVFWQDw1et5Mh9di7bGO4AWOLKFVe_Yb2RmdDwuazGXGb8zA88TTMaiEPIacPTNiVtBrIWG0EKGxHBhep9j8ujyWeCS5P9X80dBHvBNj4T9XjUwcrN6FvwegRewRMM9cBupY7jQESR7915OcbhCNyi5l64x6vVh1ZU",
"size": 2002
}
}
]
},
"sizeEstimate": 3077
}
您会看到它只是解析为 JSON 的 RFC822 消息。如果你只是遍历[=16=],把payload
当作一个part
本身,你会找到你要找的东西。
var parts = [response.payload];
while (parts.length) {
var part = parts.shift();
if (part.parts) {
parts = parts.concat(part.parts);
}
if(part.mimeType === 'text/html') {
var decodedPart = decodeURIComponent(escape(atob(part.body.data.replace(/\-/g, '+').replace(/\_/g, '/'))));
console.log(decodedPart);
}
}
我知道这个问题并不新鲜,但我写了一个 PHP 脚本,可以正确解析从 Gmail API 中提取的邮件,包括任何类型的附件。
该脚本包含一个递归 "iterateParts" 函数,它迭代所有消息部分,因此我们可以确保我们从每条消息中提取了所有可用数据。
脚本步骤为:
- 从 API
中提取所有消息 ID
- 获取一些重要信息 headers(主题和发件人地址)
- 要么 body 直接在负载上,要么将负载发送到 iterateParts
- iterateParts 正在将每条消息及其数据解析为 $msgArr,base64 编码
- 将 $msgArr 推送到主数组 $allmsgArr
- 遍历master数组,根据MIME类型和文件名将每个部分保存为文件
$maxToPull = 1;
$gmailQuery = "ALL";
// Initializing Google API
$service = new Google_Service_Gmail($client);
// Pulling all gmail messages into $messages array
$user = 'me';
$msglist = $service->users_messages->listUsersMessages($user, ["maxResults"=>$maxToPull, "q"=>$gmailQuery]);
$messages = $msglist->getMessages();
// Master array that will hold all parsed messages data, including attachments
$allmsgArr = array();
// Traverse each message
foreach($messages as $message)
{
$msgArr = array();
$single_message = $service->users_messages->get('me', $message->getId());
$payload = $single_message->getPayload();
// Nice to have the gmail msg id, can be used to direct access the message in Gmail's web gui
$msgArr['gmailmsgid'] = $message->getId();
// Retrieving the subject and "from" email address
foreach($payload->getheaders() as $oneheader)
{
if($oneheader['name'] == 'Subject')
$msgArr['subject'] = $oneheader['value'];
if($oneheader['name'] == 'From')
$msgArr['fromaddress'] = substr($oneheader['value'], strpos($oneheader['value'], '<')+1, -1);
}
// If body is directly in the message payload (only for plain text messages where there's no HTML part and no attachments, normally this is not the case)
if($payload['body']['size'] > 0)
$msgArr['textplain'] = $payload['body']['data'];
// Else, iterate over each message part and continue to dig if necessary
else
iterateParts($payload, $message->getId());
// Push the parsed $msgArr (parsed by iterateParts) to master array
array_push($allmsgArr, $msgArr);
}
// Traverse each parsed message and saving it's content and attachments to files
foreach($allmsgArr as $onemsgArr)
{
$folder = "messages/".$onemsgArr['gmailmsgid'];
mkdir($folder);
if($onemsgArr['textplain'])
file_put_contents($folder."/textplain.txt", decodeData($onemsgArr['textplain']));
if($onemsgArr['texthtml'])
file_put_contents($folder."/texthtml.html", decodeData($onemsgArr['texthtml']));
if($onemsgArr['attachments'])
{
foreach($onemsgArr['attachments'] as $oneattachment)
{
if(!empty($oneattachment['filename']))
$filename = $oneattachment['filename'];
else if($oneattachment['mimetype'] == "message/rfc822" && empty($oneattachment['filename'])) // email attachments
$filename = "noname.eml";
else
$filename = "unknown";
file_put_contents($folder."/".$filename, decodeData($oneattachment['data']));
}
}
}
function iterateParts($obj, $msgid) {
global $msgArr;
global $service;
foreach($obj as $parts)
{
// if found body data
if($parts['body']['size'] > 0)
{
// plain text representation of message body
if($parts['mimeType'] == 'text/plain')
{
$msgArr['textplain'] = $parts['body']['data'];
}
// html representation of message body
else if($parts['mimeType'] == 'text/html')
{
$msgArr['texthtml'] = $parts['body']['data'];
}
// if it's an attachment
else if(!empty($parts['body']['attachmentId']))
{
$attachArr['mimetype'] = $parts['mimeType'];
$attachArr['filename'] = $parts['filename'];
$attachArr['attachmentId'] = $parts['body']['attachmentId'];
// the message holds the attachment id, retrieve it's data from users_messages_attachments
$attachmentId_base64 = $parts['body']['attachmentId'];
$single_attachment = $service->users_messages_attachments->get('me', $msgid, $attachmentId_base64);
$attachArr['data'] = $single_attachment->getData();
$msgArr['attachments'][] = $attachArr;
}
}
// if there are other parts inside, go get them
if(!empty($parts['parts']) && !empty($parts['mimeType']) && empty($parts['body']['attachmentId']))
{
iterateParts($parts->getParts(), $msgid);
}
}
}
// All data returned from API is base64 encoded
function decodeData($data)
{
$sanitizedData = strtr($data,'-_', '+/');
return base64_decode($sanitizedData);
}
这就是 $allmsgArr 的样子(只提取了一条消息):
Array
(
[0] => Array
(
[gmailmsgid] => 25k1asfa556x2da
[fromaddress] => john@gmail.com
[subject] => Fwd: Sea gulls picture
[textplain] => UE5SIDQxQzAwMg0KDQpBUkJFTFRFU1QxDQoNCg0K
[texthtml] => PGRpdiBkaXI9Imx0ciI-PHNwYW4gc3R5bGU9ImZi
[attachments] => Array
(
[0] => Array
(
[mimetype] => image/png
[filename] => sea_gulls.png
[attachmentId] => ANGjdJ9tmy4d8vPXhU_BjNEFEaDODOpu29W2u5OTM7a0
[data] => iVBORw0KGgoAAAANSUhEUgAABSYAAAKWCAYAAABUP
)
[1] => Array
(
[mimetype] => image/jpeg
[filename] => Outlook_Signature.jpg
[attachmentId] => ANGjdJ-CgZTK0oK44Q8j7TlN_JlaexxGKZ_wHFfoEB
[data] => 6jRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEa
)
)
)
)
我使用递归函数解决了这个问题,以这种方式获取消息的所有文本,而无需导入 Json 答案的深度级别。如果需要更多解释,请告诉我。
private List<string> ObtenerTextoMensaje(IList<MessagePart> partes)
{
var listaTextos = new List<string>();
foreach(var elementoParte in partes)
{
if ((elementoParte.MimeType == "text/plain")|| (elementoParte.MimeType == "text/html"))
{
if (elementoParte.Body.Size != 0)
{
listaTextos.Add(decodificarBase64(elementoParte.Body.Data));
}
}
else
{
if(elementoParte.Parts!=null)
listaTextos = ObtenerTextoMensaje(elementoParte.Parts);
}
}
return listaTextos;
}
基于 Tholle 的想法,我已经完成了他提取 Gmail body 和 attachments 的脚本。
首先,您应该获取任何 gmail-message 对象,然后对其进行解析。
您可以使用此代码获取任何 gmail 邮件:
const {google} = require('googleapis')
// do your authenticatoin here
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectTo)
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
const response = await this.gmail.users.messages.get({
auth: oAuth2Client,
userId: 'me',
id: messageId,
format: 'full'
})
const message_obj = response.data
主脚本:
function parser(response) {
function decode(input) {
const text = new Buffer.from(input, 'base64').toString('ascii')
return decodeURIComponent(escape(text))
}
function decode_alternative(input) {
// this way does not escape special "B" characters
// const text = Buffer.from(input, 'base64').toString('ascii')
// return decodeURIComponent(escape(text))
return base64.decode(input.replace(/-/g, '+').replace(/_/g, '/'))
}
const result = {
text: '',
html: '',
attachments: []
}
let parts = [response.payload]
while (parts.length) {
let part = parts.shift()
if (part.parts)
parts = parts.concat(part.parts)
if (part.mimeType === 'text/plain')
result.text = decode(part.body.data)
if (part.mimeType === 'text/html')
result.html = decode(part.body.data)
if (part.body.attachmentId) {
result.attachments.push({
'partId': part.partId,
'mimeType': part.mimeType,
'filename': part.filename,
'body': part.body
})
}
}
return result
}
示例数据和响应:
const with_multi_type_attachments = {
"id": "16c624e85dfd9883",
"threadId": "16c62397458f34b1",
"labelIds": [],
"snippet": "This is body. Inline-attachments my-custom-link my-custom-email-address Emoji: closure.mp4",
"historyId": "14006828",
"internalDate": "1565017407000",
"payload": {
"partId": "",
"mimeType": "multipart/mixed",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 261,
"data": "VGhpcyBpcyBib2R5Lg0KDQpJbmxpbmUtYXR0YWNobWVudHMNCltpbWFnZTogMTMuanBnXQ0KbXktY3VzdG9tLWxpbmsgPGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS8-DQpteS1jdXN0b20tZW1haWwtYWRkcmVzcyA8bWVAd29yay5jb20-DQoNCkVtb2ppOg0K8J-YgvCfmI4NCg0KIGNsb3N1cmUubXA0DQo8aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xVXo4cHpoamh4UC13b0c5dFFkaGM2R1h2RGwzYUpSS3Uvdmlldz91c3A9ZHJpdmVfd2ViPg0K"
}
},
{
"partId": "0.0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 1244,
"data": "PGRpdiBkaXI9Imx0ciI-PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRpcj0ibHRyIj48ZGl2PlRoaXMgaXMgYm9keS48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PklubGluZS1hdHRhY2htZW50czxicj48L2Rpdj48ZGl2PjxkaXY-PGltZyBzcmM9ImNpZDppaV9qeXlpNjgyNjAiIGFsdD0iMTMuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OjBweCIgd2lkdGg9IjIyNSIgaGVpZ2h0PSIyMjUiPjxicj48YSBocmVmPSJodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vIiB0YXJnZXQ9Il9ibGFuayI-bXktY3VzdG9tLWxpbms8L2E-PGEgaHJlZj0ibWFpbHRvOm1lQHdvcmsuY29tIiB0YXJnZXQ9Il9ibGFuayI-PGJyPm15LWN1c3RvbS1lbWFpbC1hZGRyZXNzPC9hPjwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-RW1vamk6PGJyPjwvZGl2PjxkaXY-8J-YgvCfmI48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2IGNsYXNzPSJnbWFpbF9jaGlwIGdtYWlsX2RyaXZlX2NoaXAiIHN0eWxlPSJ3aWR0aDozOTZweDtoZWlnaHQ6MThweDttYXgtaGVpZ2h0OjE4cHg7YmFja2dyb3VuZC1jb2xvcjojZjVmNWY1O3BhZGRpbmc6NXB4O2NvbG9yOiMyMjI7Zm9udC1mYW1pbHk6YXJpYWw7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC13ZWlnaHQ6Ym9sZDtmb250LXNpemU6MTNweDtib3JkZXI6MXB4IHNvbGlkICNkZGQ7bGluZS1oZWlnaHQ6MSIgY29udGVudGVkaXRhYmxlPSJmYWxzZSI-PGEgaHJlZj0iaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xVXo4cHpoamh4UC13b0c5dFFkaGM2R1h2RGwzYUpSS3Uvdmlldz91c3A9ZHJpdmVfd2ViIiB0YXJnZXQ9Il9ibGFuayIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO21heC13aWR0aDozNjZweDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpczt3aGl0ZS1zcGFjZTpub3dyYXA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7cGFkZGluZzoxcHggMDtib3JkZXI6bm9uZSI-PGltZyBzdHlsZT0idmVydGljYWwtYWxpZ246IGJvdHRvbTsgYm9yZGVyOiBub25lOyIgc3JjPSJodHRwczovL3NzbC5nc3RhdGljLmNvbS9kb2NzL2RvY2xpc3QvaW1hZ2VzL2ljb25fMTBfZ2VuZXJpY19saXN0LnBuZyI-wqA8c3BhbiBkaXI9Imx0ciIgc3R5bGU9ImNvbG9yOiMxNWM7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dmVydGljYWwtYWxpZ246Ym90dG9tIj5jbG9zdXJlLm1wNDwvc3Bhbj48L2E-PC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo="
}
}
]
},
{
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "13.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-BkrF-Gf3L_44M9RIu8clbEbFwu0xLlfL3YOEja7y5pxZecT7YfI6hnI6-PvJ9G-w6orcWYN9jgYAOsHBCBFHezwihjdVWKBYkGB9gmdCBFVK0XKHhGODyMPJLTW2kbbhyTLFBDjEo33Tld7XMtbRAvULGV_Z6mue8XdNAfxcAnhNOGZ48Pi7y_IugKIIXZ_DAD3JvALMxctRONavk7_-oOtf09ksIQPshaA",
"size": 58159
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attachment-1.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ_M5qM10ANxuQiqn7xLsqGiB01ZfnkpzDd186JRI7NxB7l9nA2xN3EmhKygGyG4aSWeHiZeo-R9NXDG-HKWIDnuOwLoAiLLggvsQ5qlXwsGhKS7f383YaVJs8joI1Q5JtsepWPkBaBCR2wPviMt4mX_L5M7Em8GzKVtLh7fuPFbXguaHIUoyhCXP6mnKXQmNiyaatPvlB7_KPgi7h5wD9gDctpPSu59mUs-9Q",
"size": 707870
}
},
{
"partId": "2",
"mimeType": "video/mp4",
"filename": "closure.mp4",
"headers": [],
"body": {
"attachmentId": "ANGjdJ9iIPaAkkhk7mNFzOU5lQuNMRmiMxgZVY1NDsO-p6XvvbyoEqNgRqz4pepUK5HcGpOGJRMHB0ec9_wlHYbNfhr6aPvdIRO-VD4-Baw55yHgZ8KWhHAiZ3l-BY5nTB6B1xpRwEqKoun52EkPDCRf8g87tEOtq7p4ut02hg",
"size": 7261025
}
}
]
},
"sizeEstimate": 10987809
}
const with_attachmetnst = {
"id": "16c54135a9d42ab4",
"threadId": "16c5411a8c7fcaa8",
"labelIds": [],
"snippet": "With-Attachment-Body droped-ing.jpg",
"historyId": "14005159",
"internalDate": "1564778649000",
"payload": {
"partId": "",
"mimeType": "multipart/mixed",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 75,
"data": "V2l0aC1BdHRhY2htZW50LUJvZHkNCg0KDQpbaW1hZ2U6IGRyb3BlZC1pbmcuanBnXSA8aHR0cDovL2Ryb3BlZC1pbmcuanBnPg0K"
}
},
{
"partId": "0.0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 247,
"data": "PGRpdiBkaXI9Imx0ciI-PGRpdj5XaXRoLUF0dGFjaG1lbnQtQm9keTwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-PGRpdj48YSBocmVmPSJodHRwOi8vZHJvcGVkLWluZy5qcGciPjxpbWcgc3JjPSJjaWQ6aWlfanl1a3M4NnQwIiBhbHQ9ImRyb3BlZC1pbmcuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OiAwcHg7IiB3aWR0aD0iMjIzIiBoZWlnaHQ9IjIyMyI-PC9hPjxicj48L2Rpdj48L2Rpdj48L2Rpdj4NCg=="
}
}
]
},
{
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "droped-ing.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-hNpJEK_gzrYEsdQODp-Zwe5QoJP5ONsOy5JoSUC1Qk5Po7KgL_AJnMddPRZ1GWpltr-XRtXv3zS5TTUYJxf7BBZyitkMNH9Kv_rnArqXLJyBOqfL1wNqAJPeQrCzUjk6d0ahqAl6ixNyCCgTu-fxvngaBllXU5pTI3_iL6jWXoin6LoQ-a32vhKs319tChOz5GBuTCTov9oZqTtJPvj1yIqLAmUf8vochDQ",
"size": 43716
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attached-img.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-wHL_YHq_zUYZ4AyCHdstehG_7lhh21SXnzvf_33oECSiFcua3UTEbO2u5gSrEVDS4xvdnQa4e2JFb5olkMbv8rBuAprlADc_99pG_X-kf9gjhCiEIPPpr66S7VkB2Wumh9rBFc0bN6j_8mEjoGEBDAyd7lb38SiY8A6v2TP2o9gaKucYfIB__tiQ4Z1C-pSipyNmToCJfE87TuFp_ukQtDQbrVyG1bEoy2w",
"size": 44988
}
},
{
"partId": "2",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"filename": "Motivation Letter Sample.docx",
"headers": [],
"body": {
"attachmentId": "ANGjdJ9c8IKsgC8Do8iAyDHHGiyiho_zv8eI4BkaW_kvnl7--webl9P01xGWTyCwTRblgClK6dE2JH8P7hNd007ZK-CIU5Stwuc6Mp2v-KHC3slmCfko--JRrU1-EotFJqrnr3gmRw9qZgWlCqKxqiJAo1afE67LtwBSuR_frjCCUPH7RuxdY6mP_WJHSP6XA6i5uyhWaRGpnMOzawbTdq1_ZSKo9mjF__dDOsdrlQ",
"size": 7136
}
}
]
},
"sizeEstimate": 133087
}
const without_attachmetnst = {
"id": "16c579bc78fc11b1",
"threadId": "16c579b9b32bc8c5",
"labelIds": [],
"snippet": "Its Body",
"historyId": "14005084",
"internalDate": "1564837921000",
"payload": {
"partId": "",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 10,
"data": "SXRzIEJvZHkNCg=="
}
},
{
"partId": "1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 47,
"data": "PGRpdiBkaXI9Imx0ciI-SXRzIEJvZHk8YnIgY2xlYXI9ImFsbCI-PC9kaXY-DQo="
}
}
]
},
"sizeEstimate": 584
}
const only_inline_attachment = {
"id": "16c61fb94d1b287f",
"threadId": "16c60a9c8f51833b",
"labelIds": [],
"snippet": "Just-has-inline-attachments-Body",
"historyId": "14005012",
"internalDate": "1565011972000",
"payload": {
"partId": "",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 78,
"data": "SnVzdC1oYXMtaW5saW5lLWF0dGFjaG1lbnRzLUJvZHkNCg0KDQpbaW1hZ2U6IEJyb256ZS1GbG9vci1MYW1wLTYwMHg2MDAuanBnXQ0K"
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 294,
"data": "PGRpdiBkaXI9Imx0ciI-PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRpcj0ibHRyIj48ZGl2Pkp1c3QtaGFzLWlubGluZS1hdHRhY2htZW50cy1Cb2R5PC9kaXY-PGRpdj48YnI-PC9kaXY-PGRpdj48ZGl2Pjxicj48ZGl2PjxpbWcgc3JjPSJjaWQ6aWlfanl5MmtjN2IwIiBhbHQ9IkJyb256ZS1GbG9vci1MYW1wLTYwMHg2MDAuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OjBweCIgd2lkdGg9IjIyNSIgaGVpZ2h0PSIyMjUiPjxicj48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj4NCjwvZGl2PjwvZGl2Pg0K"
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "Bronze-Floor-Lamp-600x600.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-DX3652q5kW-6nNaEQL-q2zvx1Df2FV-GOfx7YfzcY2NnVkM4uMmr058QUaiAX4wsI3LybFtMB6Xaqy6ijsx2RAUg56nESumo2ecDvs-3PUgDshHJgHluwcEmpDIh7H9w6TEHteDDAs9v4jcu5xX-2_GNyeKUVK8BfGY-qYvgwVwWL5fg-TaiZ6SyrtB_w8dSOpQwtw_at25oeGRpmMh7qkLrbZ6yAkfMIXw",
"size": 91648
}
}
]
},
"sizeEstimate": 126975
}
parser(with_multi_type_attachments)
parser(with_attachmetnst)
parser(without_attachmetnst)
parser(only_inline_attachment)
结果
with_multi_type_attachments: {
"text": "This is body.\r\n\r\nInline-attachments\r\n[image: 13.jpg]\r\nmy-custom-link <http://whosebug.com/>\r\nmy-custom-email-address <me@work.com>\r\n\r\nEmoji:\r\np\u001f\u0018\u0002p\u001f\u0018\u000e\r\n\r\n closure.mp4\r\n<https://drive.google.com/file/d/1Uz8pzhjhxP-woG9tQdhc6GXvDl3aJRKu/view?usp=drive_web>\r\n",
"html": "<div dir=\"ltr\"><br><div class=\"gmail_quote\"><div dir=\"ltr\"><div>This is body.</div><div><br></div><div>Inline-attachments<br></div><div><div><img src=\"cid:ii_jyyi68260\" alt=\"13.jpg\" style=\"margin-right:0px\" width=\"225\" height=\"225\"><br><a href=\"http://whosebug.com/\" target=\"_blank\">my-custom-link</a><a href=\"mailto:me@work.com\" target=\"_blank\"><br>my-custom-email-address</a></div><div><br></div><div>Emoji:<br></div><div>p\u001f\u0018\u0002p\u001f\u0018\u000e</div><div><br></div><div class=\"gmail_chip gmail_drive_chip\" style=\"width:396px;height:18px;max-height:18px;background-color:#f5f5f5;padding:5px;color:#222;font-family:arial;font-style:normal;font-weight:bold;font-size:13px;border:1px solid #ddd;line-height:1\" contenteditable=\"false\"><a href=\"https://drive.google.com/file/d/1Uz8pzhjhxP-woG9tQdhc6GXvDl3aJRKu/view?usp=drive_web\" target=\"_blank\" style=\"display:inline-block;max-width:366px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:none;padding:1px 0;border:none\"><img style=\"vertical-align: bottom; border: none;\" src=\"https://ssl.gstatic.com/docs/doclist/images/icon_10_generic_list.png\">B <span dir=\"ltr\" style=\"color:#15c;text-decoration:none;vertical-align:bottom\">closure.mp4</span></a></div></div></div></div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attachment-1.jpg",
"body": {
"attachmentId": "ANGjdJ_M5qM10ANxuQiqn7xLsqGiB01ZfnkpzDd186JRI7NxB7l9nA2xN3EmhKygGyG4aSWeHiZeo-R9NXDG-HKWIDnuOwLoAiLLggvsQ5qlXwsGhKS7f383YaVJs8joI1Q5JtsepWPkBaBCR2wPviMt4mX_L5M7Em8GzKVtLh7fuPFbXguaHIUoyhCXP6mnKXQmNiyaatPvlB7_KPgi7h5wD9gDctpPSu59mUs-9Q",
"size": 707870
}
}, {
"partId": "2",
"mimeType": "video/mp4",
"filename": "closure.mp4",
"body": {
"attachmentId": "ANGjdJ9iIPaAkkhk7mNFzOU5lQuNMRmiMxgZVY1NDsO-p6XvvbyoEqNgRqz4pepUK5HcGpOGJRMHB0ec9_wlHYbNfhr6aPvdIRO-VD4-Baw55yHgZ8KWhHAiZ3l-BY5nTB6B1xpRwEqKoun52EkPDCRf8g87tEOtq7p4ut02hg",
"size": 7261025
}
}, {
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "13.jpg",
"body": {
"attachmentId": "ANGjdJ-BkrF-Gf3L_44M9RIu8clbEbFwu0xLlfL3YOEja7y5pxZecT7YfI6hnI6-PvJ9G-w6orcWYN9jgYAOsHBCBFHezwihjdVWKBYkGB9gmdCBFVK0XKHhGODyMPJLTW2kbbhyTLFBDjEo33Tld7XMtbRAvULGV_Z6mue8XdNAfxcAnhNOGZ48Pi7y_IugKIIXZ_DAD3JvALMxctRONavk7_-oOtf09ksIQPshaA",
"size": 58159
}
}]
}
with_attachmetnst: {
"text": "With-Attachment-Body\r\n\r\n\r\n[image: droped-ing.jpg] <http://droped-ing.jpg>\r\n",
"html": "<div dir=\"ltr\"><div>With-Attachment-Body</div><div><br></div><div><br></div><div><div><a href=\"http://droped-ing.jpg\"><img src=\"cid:ii_jyuks86t0\" alt=\"droped-ing.jpg\" style=\"margin-right: 0px;\" width=\"223\" height=\"223\"></a><br></div></div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attached-img.jpg",
"body": {
"attachmentId": "ANGjdJ-wHL_YHq_zUYZ4AyCHdstehG_7lhh21SXnzvf_33oECSiFcua3UTEbO2u5gSrEVDS4xvdnQa4e2JFb5olkMbv8rBuAprlADc_99pG_X-kf9gjhCiEIPPpr66S7VkB2Wumh9rBFc0bN6j_8mEjoGEBDAyd7lb38SiY8A6v2TP2o9gaKucYfIB__tiQ4Z1C-pSipyNmToCJfE87TuFp_ukQtDQbrVyG1bEoy2w",
"size": 44988
}
}, {
"partId": "2",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"filename": "Motivation Letter Sample.docx",
"body": {
"attachmentId": "ANGjdJ9c8IKsgC8Do8iAyDHHGiyiho_zv8eI4BkaW_kvnl7--webl9P01xGWTyCwTRblgClK6dE2JH8P7hNd007ZK-CIU5Stwuc6Mp2v-KHC3slmCfko--JRrU1-EotFJqrnr3gmRw9qZgWlCqKxqiJAo1afE67LtwBSuR_frjCCUPH7RuxdY6mP_WJHSP6XA6i5uyhWaRGpnMOzawbTdq1_ZSKo9mjF__dDOsdrlQ",
"size": 7136
}
}, {
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "droped-ing.jpg",
"body": {
"attachmentId": "ANGjdJ-hNpJEK_gzrYEsdQODp-Zwe5QoJP5ONsOy5JoSUC1Qk5Po7KgL_AJnMddPRZ1GWpltr-XRtXv3zS5TTUYJxf7BBZyitkMNH9Kv_rnArqXLJyBOqfL1wNqAJPeQrCzUjk6d0ahqAl6ixNyCCgTu-fxvngaBllXU5pTI3_iL6jWXoin6LoQ-a32vhKs319tChOz5GBuTCTov9oZqTtJPvj1yIqLAmUf8vochDQ",
"size": 43716
}
}]
}
without_attachmetnst: {
"text": "Its Body\r\n",
"html": "<div dir=\"ltr\">Its Body<br clear=\"all\"></div>\r\n",
"attachments": []
}
only_inline_attachment: {
"text": "Just-has-inline-attachments-Body\r\n\r\n\r\n[image: Bronze-Floor-Lamp-600x600.jpg]\r\n",
"html": "<div dir=\"ltr\"><br><div class=\"gmail_quote\"><div dir=\"ltr\"><div>Just-has-inline-attachments-Body</div><div><br></div><div><div><br><div><img src=\"cid:ii_jyy2kc7b0\" alt=\"Bronze-Floor-Lamp-600x600.jpg\" style=\"margin-right:0px\" width=\"225\" height=\"225\"><br></div></div></div></div>\r\n</div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "Bronze-Floor-Lamp-600x600.jpg",
"body": {
"attachmentId": "ANGjdJ-DX3652q5kW-6nNaEQL-q2zvx1Df2FV-GOfx7YfzcY2NnVkM4uMmr058QUaiAX4wsI3LybFtMB6Xaqy6ijsx2RAUg56nESumo2ecDvs-3PUgDshHJgHluwcEmpDIh7H9w6TEHteDDAs9v4jcu5xX-2_GNyeKUVK8BfGY-qYvgwVwWL5fg-TaiZ6SyrtB_w8dSOpQwtw_at25oeGRpmMh7qkLrbZ6yAkfMIXw",
"size": 91648
}
}]
}
我正在向 User.messages 端点发出请求。所有返回的对象(电子邮件)都有一个我很难理解的 mimeType 属性。
更具体地说,我希望能够根据 mimeType 提取电子邮件正文,因为我已经注意到根据 mimeType,正文将位于 body
属性 在 payload
中,或在 parts
数组中。可以返回哪些不同的 mimeType,我在哪里可以找到每种 mimeType 的电子邮件正文?
可以返回的MIME类型有很多,这里列举几种:
- text/plain: 邮件正文只有纯文本
- text/html: 消息体只在HTML
- multipart/alternative:将包含两个部分,它们可以互为替代,例如:
- text/plain 纯文本邮件正文部分
- text/html 部分用于 html 中的邮件正文
- multipart/mixed:将包含许多不相关的部分,可以是:
- multipart/alternative如上,或text/plain或text/html如上
- application/octet-stream,或其他应用程序/* 用于附件的应用程序特定 MIME 类型
- image/png ot other image/* 用于图像,可以嵌入消息中。
所有这些的权威参考是 RFC 2046 https://www.ietf.org/rfc/rfc2046.txt(您可能还想查看 2044 和 2045)
要回答您的问题,请构建消息树,然后查找:
- 第一个 text/plain 或 text/html 部分(在邮件正文或 multipart/mixed 中)
- 第一个 text/plain 或 text/html 里面的一个 multipart/alternative,它可能是 multipart 混合的一部分。
复杂消息的示例:
multipart/mixed
- multipart/alternative
- text/plain <- 纯文本邮件正文
- text/html <- HTML 中的邮件正文
- application/zip <- 一个 zip 文件附件
-
- multipart/alternative
我认为,如果您将 payload
本身视为 part
,那将是有意义的。假设我发送的消息只有一个主题和一个纯消息文本:
From: emtholin@gmail.com
To: emtholin@gmail.com
Subject: Example Subject
This is the plain text message
这将导致以下经过解析的消息:
{
"id": "154ecb53c10b74d8",
"threadId": "154ecb53c10b74d8",
"labelIds": [
"INBOX",
"SENT"
],
"snippet": "This is the plain text message",
"historyId": "38877",
"internalDate": "1464260181000",
"payload": {
"partId": "",
"mimeType": "text/plain",
"filename": "",
"headers": [
...
],
"body": {
"size": 31,
"data": "VGhpcyBpcyB0aGUgcGxhaW4gdGV4dCBtZXNzYWdlCg=="
}
},
"sizeEstimate": 355
}
如果我发送的消息包含纯文本部分、html 部分和图像,解析后将如下所示:
{
"id": "154ed5ccaa12f3df",
"threadId": "154ed5ccaa12f3df",
"labelIds": [
"SENT",
"INBOX",
"IMPORTANT"
],
"snippet": "This is a plain/html message with an image.",
"historyId": "841379",
"internalDate": "1464271162000",
"payload": {
"mimeType": "multipart/mixed",
"filename": "",
"headers": [
...
],
"body": {
"size": 0
},
"parts": [
{
"mimeType": "multipart/alternative",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "multipart/alternative; boundary=089e0122896c7c80d80533bf3205"
}
],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
}
],
"body": {
"size": 47,
"data": "VGhpcyBpcyBhIHBsYWluL2h0bWwgKm1lc3NhZ2UqIHdpdGggYW4gaW1hZ2UuDQo="
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/html; charset=UTF-8"
}
],
"body": {
"size": 73,
"data": "PGRpdiBkaXI9Imx0ciI-VGhpcyBpcyBhIHBsYWluL2h0bWwgPGI-bWVzc2FnZTwvYj4gd2l0aCBhbiBpbWFnZS48L2Rpdj4NCg=="
}
}
]
},
{
"partId": "1",
"mimeType": "image/png",
"filename": "smile.png",
"headers": [
...
],
"body": {
"attachmentId": "ANGjdJ-OrSy7VAYL-UbRyNtmySbZLlV-fV43zJF0_neNGZ8yKugsZAxb32eSb-CrbYIhF9NvjGwBVEjSkRrUWoCS7aDpgoQnt9WR7f2sa17qVEyOg_JVSbrGrunirvQw2dY-SxxB3Y0JP3aYDHSBXpNO6fFCByVFWQDw1et5Mh9di7bGO4AWOLKFVe_Yb2RmdDwuazGXGb8zA88TTMaiEPIacPTNiVtBrIWG0EKGxHBhep9j8ujyWeCS5P9X80dBHvBNj4T9XjUwcrN6FvwegRewRMM9cBupY7jQESR7915OcbhCNyi5l64x6vVh1ZU",
"size": 2002
}
}
]
},
"sizeEstimate": 3077
}
您会看到它只是解析为 JSON 的 RFC822 消息。如果你只是遍历[=16=],把payload
当作一个part
本身,你会找到你要找的东西。
var parts = [response.payload];
while (parts.length) {
var part = parts.shift();
if (part.parts) {
parts = parts.concat(part.parts);
}
if(part.mimeType === 'text/html') {
var decodedPart = decodeURIComponent(escape(atob(part.body.data.replace(/\-/g, '+').replace(/\_/g, '/'))));
console.log(decodedPart);
}
}
我知道这个问题并不新鲜,但我写了一个 PHP 脚本,可以正确解析从 Gmail API 中提取的邮件,包括任何类型的附件。
该脚本包含一个递归 "iterateParts" 函数,它迭代所有消息部分,因此我们可以确保我们从每条消息中提取了所有可用数据。
脚本步骤为:
- 从 API 中提取所有消息 ID
- 获取一些重要信息 headers(主题和发件人地址)
- 要么 body 直接在负载上,要么将负载发送到 iterateParts
- iterateParts 正在将每条消息及其数据解析为 $msgArr,base64 编码
- 将 $msgArr 推送到主数组 $allmsgArr
- 遍历master数组,根据MIME类型和文件名将每个部分保存为文件
$maxToPull = 1; $gmailQuery = "ALL"; // Initializing Google API $service = new Google_Service_Gmail($client); // Pulling all gmail messages into $messages array $user = 'me'; $msglist = $service->users_messages->listUsersMessages($user, ["maxResults"=>$maxToPull, "q"=>$gmailQuery]); $messages = $msglist->getMessages(); // Master array that will hold all parsed messages data, including attachments $allmsgArr = array(); // Traverse each message foreach($messages as $message) { $msgArr = array(); $single_message = $service->users_messages->get('me', $message->getId()); $payload = $single_message->getPayload(); // Nice to have the gmail msg id, can be used to direct access the message in Gmail's web gui $msgArr['gmailmsgid'] = $message->getId(); // Retrieving the subject and "from" email address foreach($payload->getheaders() as $oneheader) { if($oneheader['name'] == 'Subject') $msgArr['subject'] = $oneheader['value']; if($oneheader['name'] == 'From') $msgArr['fromaddress'] = substr($oneheader['value'], strpos($oneheader['value'], '<')+1, -1); } // If body is directly in the message payload (only for plain text messages where there's no HTML part and no attachments, normally this is not the case) if($payload['body']['size'] > 0) $msgArr['textplain'] = $payload['body']['data']; // Else, iterate over each message part and continue to dig if necessary else iterateParts($payload, $message->getId()); // Push the parsed $msgArr (parsed by iterateParts) to master array array_push($allmsgArr, $msgArr); } // Traverse each parsed message and saving it's content and attachments to files foreach($allmsgArr as $onemsgArr) { $folder = "messages/".$onemsgArr['gmailmsgid']; mkdir($folder); if($onemsgArr['textplain']) file_put_contents($folder."/textplain.txt", decodeData($onemsgArr['textplain'])); if($onemsgArr['texthtml']) file_put_contents($folder."/texthtml.html", decodeData($onemsgArr['texthtml'])); if($onemsgArr['attachments']) { foreach($onemsgArr['attachments'] as $oneattachment) { if(!empty($oneattachment['filename'])) $filename = $oneattachment['filename']; else if($oneattachment['mimetype'] == "message/rfc822" && empty($oneattachment['filename'])) // email attachments $filename = "noname.eml"; else $filename = "unknown"; file_put_contents($folder."/".$filename, decodeData($oneattachment['data'])); } } } function iterateParts($obj, $msgid) { global $msgArr; global $service; foreach($obj as $parts) { // if found body data if($parts['body']['size'] > 0) { // plain text representation of message body if($parts['mimeType'] == 'text/plain') { $msgArr['textplain'] = $parts['body']['data']; } // html representation of message body else if($parts['mimeType'] == 'text/html') { $msgArr['texthtml'] = $parts['body']['data']; } // if it's an attachment else if(!empty($parts['body']['attachmentId'])) { $attachArr['mimetype'] = $parts['mimeType']; $attachArr['filename'] = $parts['filename']; $attachArr['attachmentId'] = $parts['body']['attachmentId']; // the message holds the attachment id, retrieve it's data from users_messages_attachments $attachmentId_base64 = $parts['body']['attachmentId']; $single_attachment = $service->users_messages_attachments->get('me', $msgid, $attachmentId_base64); $attachArr['data'] = $single_attachment->getData(); $msgArr['attachments'][] = $attachArr; } } // if there are other parts inside, go get them if(!empty($parts['parts']) && !empty($parts['mimeType']) && empty($parts['body']['attachmentId'])) { iterateParts($parts->getParts(), $msgid); } } } // All data returned from API is base64 encoded function decodeData($data) { $sanitizedData = strtr($data,'-_', '+/'); return base64_decode($sanitizedData); }
这就是 $allmsgArr 的样子(只提取了一条消息):
Array ( [0] => Array ( [gmailmsgid] => 25k1asfa556x2da [fromaddress] => john@gmail.com [subject] => Fwd: Sea gulls picture [textplain] => UE5SIDQxQzAwMg0KDQpBUkJFTFRFU1QxDQoNCg0K [texthtml] => PGRpdiBkaXI9Imx0ciI-PHNwYW4gc3R5bGU9ImZi [attachments] => Array ( [0] => Array ( [mimetype] => image/png [filename] => sea_gulls.png [attachmentId] => ANGjdJ9tmy4d8vPXhU_BjNEFEaDODOpu29W2u5OTM7a0 [data] => iVBORw0KGgoAAAANSUhEUgAABSYAAAKWCAYAAABUP ) [1] => Array ( [mimetype] => image/jpeg [filename] => Outlook_Signature.jpg [attachmentId] => ANGjdJ-CgZTK0oK44Q8j7TlN_JlaexxGKZ_wHFfoEB [data] => 6jRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEa ) ) ) )
我使用递归函数解决了这个问题,以这种方式获取消息的所有文本,而无需导入 Json 答案的深度级别。如果需要更多解释,请告诉我。
private List<string> ObtenerTextoMensaje(IList<MessagePart> partes)
{
var listaTextos = new List<string>();
foreach(var elementoParte in partes)
{
if ((elementoParte.MimeType == "text/plain")|| (elementoParte.MimeType == "text/html"))
{
if (elementoParte.Body.Size != 0)
{
listaTextos.Add(decodificarBase64(elementoParte.Body.Data));
}
}
else
{
if(elementoParte.Parts!=null)
listaTextos = ObtenerTextoMensaje(elementoParte.Parts);
}
}
return listaTextos;
}
基于 Tholle 的想法,我已经完成了他提取 Gmail body 和 attachments 的脚本。
首先,您应该获取任何 gmail-message 对象,然后对其进行解析。 您可以使用此代码获取任何 gmail 邮件:
const {google} = require('googleapis')
// do your authenticatoin here
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectTo)
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
const response = await this.gmail.users.messages.get({
auth: oAuth2Client,
userId: 'me',
id: messageId,
format: 'full'
})
const message_obj = response.data
主脚本:
function parser(response) {
function decode(input) {
const text = new Buffer.from(input, 'base64').toString('ascii')
return decodeURIComponent(escape(text))
}
function decode_alternative(input) {
// this way does not escape special "B" characters
// const text = Buffer.from(input, 'base64').toString('ascii')
// return decodeURIComponent(escape(text))
return base64.decode(input.replace(/-/g, '+').replace(/_/g, '/'))
}
const result = {
text: '',
html: '',
attachments: []
}
let parts = [response.payload]
while (parts.length) {
let part = parts.shift()
if (part.parts)
parts = parts.concat(part.parts)
if (part.mimeType === 'text/plain')
result.text = decode(part.body.data)
if (part.mimeType === 'text/html')
result.html = decode(part.body.data)
if (part.body.attachmentId) {
result.attachments.push({
'partId': part.partId,
'mimeType': part.mimeType,
'filename': part.filename,
'body': part.body
})
}
}
return result
}
示例数据和响应:
const with_multi_type_attachments = {
"id": "16c624e85dfd9883",
"threadId": "16c62397458f34b1",
"labelIds": [],
"snippet": "This is body. Inline-attachments my-custom-link my-custom-email-address Emoji: closure.mp4",
"historyId": "14006828",
"internalDate": "1565017407000",
"payload": {
"partId": "",
"mimeType": "multipart/mixed",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 261,
"data": "VGhpcyBpcyBib2R5Lg0KDQpJbmxpbmUtYXR0YWNobWVudHMNCltpbWFnZTogMTMuanBnXQ0KbXktY3VzdG9tLWxpbmsgPGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS8-DQpteS1jdXN0b20tZW1haWwtYWRkcmVzcyA8bWVAd29yay5jb20-DQoNCkVtb2ppOg0K8J-YgvCfmI4NCg0KIGNsb3N1cmUubXA0DQo8aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xVXo4cHpoamh4UC13b0c5dFFkaGM2R1h2RGwzYUpSS3Uvdmlldz91c3A9ZHJpdmVfd2ViPg0K"
}
},
{
"partId": "0.0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 1244,
"data": "PGRpdiBkaXI9Imx0ciI-PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRpcj0ibHRyIj48ZGl2PlRoaXMgaXMgYm9keS48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PklubGluZS1hdHRhY2htZW50czxicj48L2Rpdj48ZGl2PjxkaXY-PGltZyBzcmM9ImNpZDppaV9qeXlpNjgyNjAiIGFsdD0iMTMuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OjBweCIgd2lkdGg9IjIyNSIgaGVpZ2h0PSIyMjUiPjxicj48YSBocmVmPSJodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vIiB0YXJnZXQ9Il9ibGFuayI-bXktY3VzdG9tLWxpbms8L2E-PGEgaHJlZj0ibWFpbHRvOm1lQHdvcmsuY29tIiB0YXJnZXQ9Il9ibGFuayI-PGJyPm15LWN1c3RvbS1lbWFpbC1hZGRyZXNzPC9hPjwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-RW1vamk6PGJyPjwvZGl2PjxkaXY-8J-YgvCfmI48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2IGNsYXNzPSJnbWFpbF9jaGlwIGdtYWlsX2RyaXZlX2NoaXAiIHN0eWxlPSJ3aWR0aDozOTZweDtoZWlnaHQ6MThweDttYXgtaGVpZ2h0OjE4cHg7YmFja2dyb3VuZC1jb2xvcjojZjVmNWY1O3BhZGRpbmc6NXB4O2NvbG9yOiMyMjI7Zm9udC1mYW1pbHk6YXJpYWw7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC13ZWlnaHQ6Ym9sZDtmb250LXNpemU6MTNweDtib3JkZXI6MXB4IHNvbGlkICNkZGQ7bGluZS1oZWlnaHQ6MSIgY29udGVudGVkaXRhYmxlPSJmYWxzZSI-PGEgaHJlZj0iaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xVXo4cHpoamh4UC13b0c5dFFkaGM2R1h2RGwzYUpSS3Uvdmlldz91c3A9ZHJpdmVfd2ViIiB0YXJnZXQ9Il9ibGFuayIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO21heC13aWR0aDozNjZweDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpczt3aGl0ZS1zcGFjZTpub3dyYXA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7cGFkZGluZzoxcHggMDtib3JkZXI6bm9uZSI-PGltZyBzdHlsZT0idmVydGljYWwtYWxpZ246IGJvdHRvbTsgYm9yZGVyOiBub25lOyIgc3JjPSJodHRwczovL3NzbC5nc3RhdGljLmNvbS9kb2NzL2RvY2xpc3QvaW1hZ2VzL2ljb25fMTBfZ2VuZXJpY19saXN0LnBuZyI-wqA8c3BhbiBkaXI9Imx0ciIgc3R5bGU9ImNvbG9yOiMxNWM7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dmVydGljYWwtYWxpZ246Ym90dG9tIj5jbG9zdXJlLm1wNDwvc3Bhbj48L2E-PC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo="
}
}
]
},
{
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "13.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-BkrF-Gf3L_44M9RIu8clbEbFwu0xLlfL3YOEja7y5pxZecT7YfI6hnI6-PvJ9G-w6orcWYN9jgYAOsHBCBFHezwihjdVWKBYkGB9gmdCBFVK0XKHhGODyMPJLTW2kbbhyTLFBDjEo33Tld7XMtbRAvULGV_Z6mue8XdNAfxcAnhNOGZ48Pi7y_IugKIIXZ_DAD3JvALMxctRONavk7_-oOtf09ksIQPshaA",
"size": 58159
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attachment-1.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ_M5qM10ANxuQiqn7xLsqGiB01ZfnkpzDd186JRI7NxB7l9nA2xN3EmhKygGyG4aSWeHiZeo-R9NXDG-HKWIDnuOwLoAiLLggvsQ5qlXwsGhKS7f383YaVJs8joI1Q5JtsepWPkBaBCR2wPviMt4mX_L5M7Em8GzKVtLh7fuPFbXguaHIUoyhCXP6mnKXQmNiyaatPvlB7_KPgi7h5wD9gDctpPSu59mUs-9Q",
"size": 707870
}
},
{
"partId": "2",
"mimeType": "video/mp4",
"filename": "closure.mp4",
"headers": [],
"body": {
"attachmentId": "ANGjdJ9iIPaAkkhk7mNFzOU5lQuNMRmiMxgZVY1NDsO-p6XvvbyoEqNgRqz4pepUK5HcGpOGJRMHB0ec9_wlHYbNfhr6aPvdIRO-VD4-Baw55yHgZ8KWhHAiZ3l-BY5nTB6B1xpRwEqKoun52EkPDCRf8g87tEOtq7p4ut02hg",
"size": 7261025
}
}
]
},
"sizeEstimate": 10987809
}
const with_attachmetnst = {
"id": "16c54135a9d42ab4",
"threadId": "16c5411a8c7fcaa8",
"labelIds": [],
"snippet": "With-Attachment-Body droped-ing.jpg",
"historyId": "14005159",
"internalDate": "1564778649000",
"payload": {
"partId": "",
"mimeType": "multipart/mixed",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 75,
"data": "V2l0aC1BdHRhY2htZW50LUJvZHkNCg0KDQpbaW1hZ2U6IGRyb3BlZC1pbmcuanBnXSA8aHR0cDovL2Ryb3BlZC1pbmcuanBnPg0K"
}
},
{
"partId": "0.0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 247,
"data": "PGRpdiBkaXI9Imx0ciI-PGRpdj5XaXRoLUF0dGFjaG1lbnQtQm9keTwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-PGRpdj48YSBocmVmPSJodHRwOi8vZHJvcGVkLWluZy5qcGciPjxpbWcgc3JjPSJjaWQ6aWlfanl1a3M4NnQwIiBhbHQ9ImRyb3BlZC1pbmcuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OiAwcHg7IiB3aWR0aD0iMjIzIiBoZWlnaHQ9IjIyMyI-PC9hPjxicj48L2Rpdj48L2Rpdj48L2Rpdj4NCg=="
}
}
]
},
{
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "droped-ing.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-hNpJEK_gzrYEsdQODp-Zwe5QoJP5ONsOy5JoSUC1Qk5Po7KgL_AJnMddPRZ1GWpltr-XRtXv3zS5TTUYJxf7BBZyitkMNH9Kv_rnArqXLJyBOqfL1wNqAJPeQrCzUjk6d0ahqAl6ixNyCCgTu-fxvngaBllXU5pTI3_iL6jWXoin6LoQ-a32vhKs319tChOz5GBuTCTov9oZqTtJPvj1yIqLAmUf8vochDQ",
"size": 43716
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attached-img.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-wHL_YHq_zUYZ4AyCHdstehG_7lhh21SXnzvf_33oECSiFcua3UTEbO2u5gSrEVDS4xvdnQa4e2JFb5olkMbv8rBuAprlADc_99pG_X-kf9gjhCiEIPPpr66S7VkB2Wumh9rBFc0bN6j_8mEjoGEBDAyd7lb38SiY8A6v2TP2o9gaKucYfIB__tiQ4Z1C-pSipyNmToCJfE87TuFp_ukQtDQbrVyG1bEoy2w",
"size": 44988
}
},
{
"partId": "2",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"filename": "Motivation Letter Sample.docx",
"headers": [],
"body": {
"attachmentId": "ANGjdJ9c8IKsgC8Do8iAyDHHGiyiho_zv8eI4BkaW_kvnl7--webl9P01xGWTyCwTRblgClK6dE2JH8P7hNd007ZK-CIU5Stwuc6Mp2v-KHC3slmCfko--JRrU1-EotFJqrnr3gmRw9qZgWlCqKxqiJAo1afE67LtwBSuR_frjCCUPH7RuxdY6mP_WJHSP6XA6i5uyhWaRGpnMOzawbTdq1_ZSKo9mjF__dDOsdrlQ",
"size": 7136
}
}
]
},
"sizeEstimate": 133087
}
const without_attachmetnst = {
"id": "16c579bc78fc11b1",
"threadId": "16c579b9b32bc8c5",
"labelIds": [],
"snippet": "Its Body",
"historyId": "14005084",
"internalDate": "1564837921000",
"payload": {
"partId": "",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 10,
"data": "SXRzIEJvZHkNCg=="
}
},
{
"partId": "1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 47,
"data": "PGRpdiBkaXI9Imx0ciI-SXRzIEJvZHk8YnIgY2xlYXI9ImFsbCI-PC9kaXY-DQo="
}
}
]
},
"sizeEstimate": 584
}
const only_inline_attachment = {
"id": "16c61fb94d1b287f",
"threadId": "16c60a9c8f51833b",
"labelIds": [],
"snippet": "Just-has-inline-attachments-Body",
"historyId": "14005012",
"internalDate": "1565011972000",
"payload": {
"partId": "",
"mimeType": "multipart/related",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0",
"mimeType": "multipart/alternative",
"filename": "",
"headers": [],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [],
"body": {
"size": 78,
"data": "SnVzdC1oYXMtaW5saW5lLWF0dGFjaG1lbnRzLUJvZHkNCg0KDQpbaW1hZ2U6IEJyb256ZS1GbG9vci1MYW1wLTYwMHg2MDAuanBnXQ0K"
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [],
"body": {
"size": 294,
"data": "PGRpdiBkaXI9Imx0ciI-PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRpcj0ibHRyIj48ZGl2Pkp1c3QtaGFzLWlubGluZS1hdHRhY2htZW50cy1Cb2R5PC9kaXY-PGRpdj48YnI-PC9kaXY-PGRpdj48ZGl2Pjxicj48ZGl2PjxpbWcgc3JjPSJjaWQ6aWlfanl5MmtjN2IwIiBhbHQ9IkJyb256ZS1GbG9vci1MYW1wLTYwMHg2MDAuanBnIiBzdHlsZT0ibWFyZ2luLXJpZ2h0OjBweCIgd2lkdGg9IjIyNSIgaGVpZ2h0PSIyMjUiPjxicj48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj4NCjwvZGl2PjwvZGl2Pg0K"
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "Bronze-Floor-Lamp-600x600.jpg",
"headers": [],
"body": {
"attachmentId": "ANGjdJ-DX3652q5kW-6nNaEQL-q2zvx1Df2FV-GOfx7YfzcY2NnVkM4uMmr058QUaiAX4wsI3LybFtMB6Xaqy6ijsx2RAUg56nESumo2ecDvs-3PUgDshHJgHluwcEmpDIh7H9w6TEHteDDAs9v4jcu5xX-2_GNyeKUVK8BfGY-qYvgwVwWL5fg-TaiZ6SyrtB_w8dSOpQwtw_at25oeGRpmMh7qkLrbZ6yAkfMIXw",
"size": 91648
}
}
]
},
"sizeEstimate": 126975
}
parser(with_multi_type_attachments)
parser(with_attachmetnst)
parser(without_attachmetnst)
parser(only_inline_attachment)
结果
with_multi_type_attachments: {
"text": "This is body.\r\n\r\nInline-attachments\r\n[image: 13.jpg]\r\nmy-custom-link <http://whosebug.com/>\r\nmy-custom-email-address <me@work.com>\r\n\r\nEmoji:\r\np\u001f\u0018\u0002p\u001f\u0018\u000e\r\n\r\n closure.mp4\r\n<https://drive.google.com/file/d/1Uz8pzhjhxP-woG9tQdhc6GXvDl3aJRKu/view?usp=drive_web>\r\n",
"html": "<div dir=\"ltr\"><br><div class=\"gmail_quote\"><div dir=\"ltr\"><div>This is body.</div><div><br></div><div>Inline-attachments<br></div><div><div><img src=\"cid:ii_jyyi68260\" alt=\"13.jpg\" style=\"margin-right:0px\" width=\"225\" height=\"225\"><br><a href=\"http://whosebug.com/\" target=\"_blank\">my-custom-link</a><a href=\"mailto:me@work.com\" target=\"_blank\"><br>my-custom-email-address</a></div><div><br></div><div>Emoji:<br></div><div>p\u001f\u0018\u0002p\u001f\u0018\u000e</div><div><br></div><div class=\"gmail_chip gmail_drive_chip\" style=\"width:396px;height:18px;max-height:18px;background-color:#f5f5f5;padding:5px;color:#222;font-family:arial;font-style:normal;font-weight:bold;font-size:13px;border:1px solid #ddd;line-height:1\" contenteditable=\"false\"><a href=\"https://drive.google.com/file/d/1Uz8pzhjhxP-woG9tQdhc6GXvDl3aJRKu/view?usp=drive_web\" target=\"_blank\" style=\"display:inline-block;max-width:366px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:none;padding:1px 0;border:none\"><img style=\"vertical-align: bottom; border: none;\" src=\"https://ssl.gstatic.com/docs/doclist/images/icon_10_generic_list.png\">B <span dir=\"ltr\" style=\"color:#15c;text-decoration:none;vertical-align:bottom\">closure.mp4</span></a></div></div></div></div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attachment-1.jpg",
"body": {
"attachmentId": "ANGjdJ_M5qM10ANxuQiqn7xLsqGiB01ZfnkpzDd186JRI7NxB7l9nA2xN3EmhKygGyG4aSWeHiZeo-R9NXDG-HKWIDnuOwLoAiLLggvsQ5qlXwsGhKS7f383YaVJs8joI1Q5JtsepWPkBaBCR2wPviMt4mX_L5M7Em8GzKVtLh7fuPFbXguaHIUoyhCXP6mnKXQmNiyaatPvlB7_KPgi7h5wD9gDctpPSu59mUs-9Q",
"size": 707870
}
}, {
"partId": "2",
"mimeType": "video/mp4",
"filename": "closure.mp4",
"body": {
"attachmentId": "ANGjdJ9iIPaAkkhk7mNFzOU5lQuNMRmiMxgZVY1NDsO-p6XvvbyoEqNgRqz4pepUK5HcGpOGJRMHB0ec9_wlHYbNfhr6aPvdIRO-VD4-Baw55yHgZ8KWhHAiZ3l-BY5nTB6B1xpRwEqKoun52EkPDCRf8g87tEOtq7p4ut02hg",
"size": 7261025
}
}, {
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "13.jpg",
"body": {
"attachmentId": "ANGjdJ-BkrF-Gf3L_44M9RIu8clbEbFwu0xLlfL3YOEja7y5pxZecT7YfI6hnI6-PvJ9G-w6orcWYN9jgYAOsHBCBFHezwihjdVWKBYkGB9gmdCBFVK0XKHhGODyMPJLTW2kbbhyTLFBDjEo33Tld7XMtbRAvULGV_Z6mue8XdNAfxcAnhNOGZ48Pi7y_IugKIIXZ_DAD3JvALMxctRONavk7_-oOtf09ksIQPshaA",
"size": 58159
}
}]
}
with_attachmetnst: {
"text": "With-Attachment-Body\r\n\r\n\r\n[image: droped-ing.jpg] <http://droped-ing.jpg>\r\n",
"html": "<div dir=\"ltr\"><div>With-Attachment-Body</div><div><br></div><div><br></div><div><div><a href=\"http://droped-ing.jpg\"><img src=\"cid:ii_jyuks86t0\" alt=\"droped-ing.jpg\" style=\"margin-right: 0px;\" width=\"223\" height=\"223\"></a><br></div></div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "attached-img.jpg",
"body": {
"attachmentId": "ANGjdJ-wHL_YHq_zUYZ4AyCHdstehG_7lhh21SXnzvf_33oECSiFcua3UTEbO2u5gSrEVDS4xvdnQa4e2JFb5olkMbv8rBuAprlADc_99pG_X-kf9gjhCiEIPPpr66S7VkB2Wumh9rBFc0bN6j_8mEjoGEBDAyd7lb38SiY8A6v2TP2o9gaKucYfIB__tiQ4Z1C-pSipyNmToCJfE87TuFp_ukQtDQbrVyG1bEoy2w",
"size": 44988
}
}, {
"partId": "2",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"filename": "Motivation Letter Sample.docx",
"body": {
"attachmentId": "ANGjdJ9c8IKsgC8Do8iAyDHHGiyiho_zv8eI4BkaW_kvnl7--webl9P01xGWTyCwTRblgClK6dE2JH8P7hNd007ZK-CIU5Stwuc6Mp2v-KHC3slmCfko--JRrU1-EotFJqrnr3gmRw9qZgWlCqKxqiJAo1afE67LtwBSuR_frjCCUPH7RuxdY6mP_WJHSP6XA6i5uyhWaRGpnMOzawbTdq1_ZSKo9mjF__dDOsdrlQ",
"size": 7136
}
}, {
"partId": "0.1",
"mimeType": "image/jpeg",
"filename": "droped-ing.jpg",
"body": {
"attachmentId": "ANGjdJ-hNpJEK_gzrYEsdQODp-Zwe5QoJP5ONsOy5JoSUC1Qk5Po7KgL_AJnMddPRZ1GWpltr-XRtXv3zS5TTUYJxf7BBZyitkMNH9Kv_rnArqXLJyBOqfL1wNqAJPeQrCzUjk6d0ahqAl6ixNyCCgTu-fxvngaBllXU5pTI3_iL6jWXoin6LoQ-a32vhKs319tChOz5GBuTCTov9oZqTtJPvj1yIqLAmUf8vochDQ",
"size": 43716
}
}]
}
without_attachmetnst: {
"text": "Its Body\r\n",
"html": "<div dir=\"ltr\">Its Body<br clear=\"all\"></div>\r\n",
"attachments": []
}
only_inline_attachment: {
"text": "Just-has-inline-attachments-Body\r\n\r\n\r\n[image: Bronze-Floor-Lamp-600x600.jpg]\r\n",
"html": "<div dir=\"ltr\"><br><div class=\"gmail_quote\"><div dir=\"ltr\"><div>Just-has-inline-attachments-Body</div><div><br></div><div><div><br><div><img src=\"cid:ii_jyy2kc7b0\" alt=\"Bronze-Floor-Lamp-600x600.jpg\" style=\"margin-right:0px\" width=\"225\" height=\"225\"><br></div></div></div></div>\r\n</div></div>\r\n",
"attachments": [{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "Bronze-Floor-Lamp-600x600.jpg",
"body": {
"attachmentId": "ANGjdJ-DX3652q5kW-6nNaEQL-q2zvx1Df2FV-GOfx7YfzcY2NnVkM4uMmr058QUaiAX4wsI3LybFtMB6Xaqy6ijsx2RAUg56nESumo2ecDvs-3PUgDshHJgHluwcEmpDIh7H9w6TEHteDDAs9v4jcu5xX-2_GNyeKUVK8BfGY-qYvgwVwWL5fg-TaiZ6SyrtB_w8dSOpQwtw_at25oeGRpmMh7qkLrbZ6yAkfMIXw",
"size": 91648
}
}]
}