根据 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" 函数,它迭代所有消息部分,因此我们可以确保我们从每条消息中提取了所有可用数据。

脚本步骤为:

  1. 从 API
  2. 中提取所有消息 ID
  3. 获取一些重要信息 headers(主题和发件人地址)
  4. 要么 body 直接在负载上,要么将负载发送到 iterateParts
  5. iterateParts 正在将每条消息及其数据解析为 $msgArr,base64 编码
  6. 将 $msgArr 推送到主数组 $allmsgArr
  7. 遍历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 bodyattachments 的脚本。

首先,您应该获取任何 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
    }
  }]
}