在 Google Apps 脚本中使用高级 Gmail 服务创建与表情符号兼容的 Gmail 草稿

Creating an Emoji-Compatible Gmail draft with Advanced Gmail Service in Google Apps Script

我正在尝试 create a Gmail draft with the Gmail Advance Service. What I need is to have the body contain data in bytes format。我构建了以下函数:

const createDraftWithAdvancedService = () => {
  Gmail.Users.Drafts.create({
    message: {
      payload: {
        parts: [
          {
            body: {
              data: [
                42,
                123,
                123,
                80,
                114,
              ],
            }
          }
        ],
        headers: [
          {
            "value": "This is a test subject",
            "name": "Subject"
          },
        ]
      }
    }
  }, 'user@domain.com');
}

但是,当我 运行 它时,出现以下错误:

GoogleJsonResponseException: API call to gmail.users.drafts.create failed with error: Invalid JSON payload received. Unknown name "data" at 'draft.message.payload.parts[0].body': Proto field is not repeating, cannot start list.

错误看起来很奇怪,因为我正确地遵循了索引(或者我相信在检查了一百次之后)。

我在这里错过了什么?

更新

我将字节字符串格式化为数组的原因是,当您 阅读 草稿时,Gmail API return 就是这样。如果您有不同的 工作 代码,我洗耳恭听。而且我不能使用raw,我需要设置字节串。

以下是如何检索消息对象及其格式的示例:

我的消息草稿:

检索此草稿消息的脚本:

const getMessage = () => {
  const id = 'r-8326849559354985208';
  const msg = Gmail.Users.Drafts.get('user@domain.com', id);
  console.log(JSON.stringify(msg, null, 2));
}

脚本的输出:

{
  "message": {
    "internalDate": "1633701716000",
    "snippet": "Draft body",
    "labelIds": [
      "DRAFT"
    ],
    "historyId": "954861",
    "sizeEstimate": 534,
    "payload": {
      "filename": "",
      "parts": [
        {
          "partId": "0",
          "headers": [
            {
              "value": "text/plain; charset=\"UTF-8\"",
              "name": "Content-Type"
            }
          ],
          "filename": "",
          "body": {
            "data": [
              68,
              114,
              97,
              102,
              116,
              32,
              98,
              111,
              100,
              121,
              13,
              10
            ],
            "size": 12
          },
          "mimeType": "text/plain"
        },
        {
          "headers": [
            {
              "value": "text/html; charset=\"UTF-8\"",
              "name": "Content-Type"
            }
          ],
          "partId": "1",
          "body": {
            "size": 33,
            "data": [
              60,
              100,
              105,
              118,
              32,
              100,
              105,
              114,
              61,
              34,
              108,
              116,
              114,
              34,
              62,
              68,
              114,
              97,
              102,
              116,
              32,
              98,
              111,
              100,
              121,
              60,
              47,
              100,
              105,
              118,
              62,
              13,
              10
            ]
          },
          "mimeType": "text/html",
          "filename": ""
        }
      ],
      "body": {
        "size": 0
      },
      "headers": [
        {
          "value": "1.0",
          "name": "MIME-Version"
        },
        {
          "value": "Fri, 8 Oct 2021 16:01:56 +0200",
          "name": "Date"
        },
        {
          "value": "<CADVhnimBt3Jdod1wBgGUgB_75yrsoJMwM68mtYKmX6cN39=CNQ@mail.gmail.com>",
          "name": "Message-ID"
        },
        {
          "name": "Subject",
          "value": "Draft subject"
        },
        {
          "name": "From",
          "value": "\"KOSTYUK, Dmitry\" <user@domain.com>"
        },
        {
          "value": "multipart/alternative; boundary=\"00000000000088918105cdd7d2e1\"",
          "name": "Content-Type"
        }
      ],
      "mimeType": "multipart/alternative",
      "partId": ""
    },
    "id": "17c6035e45454be8",
    "threadId": "17c6035c50e83b2f"
  },
  "id": "r-8326849559354985208"
}

经过一番研究和学习RFC2822 MIMEText语法后,我对这个问题有了明确的答案。我分三部分回答:

  1. 什么没用
  2. 解决方案一:困难的方法
  3. 解决方案二:新的简便方法

什么没用

没有用的是像我在问题中所做的那样使用实际的 Message object。不要问我为什么,它没有在任何地方记录,它只是不起作用。即使您通过 Gmail.Users.Drafts.get() 从另一条消息中复制 JSON object 的全部或现有部分,GAS 仍会抛出错误。

所以进入的是而不是返回的内容,即使文档另有说明。

因此唯一的解决方案是使用消息 object 的 raw 属性,它必须是 RFC2822 格式的 base-64 编码字符串。

解决方案一:困难的方法

结合 here and here 中的解决方案,创建了一个基本函数,可以生成带有表情符号的消息草稿:

function convert(toEmail, fromEmail, subject, body) {
  body = Utilities.base64Encode(body, Utilities.Charset.UTF_8);
  subject = Utilities.base64Encode(subject, Utilities.Charset.UTF_8);
  const boundary = "boundaryboundary";
  const mailData = [
    "MIME-Version: 1.0",
    "To: " + toEmail,
    "From: " + fromEmail,
    "Subject: =?utf-8?B?" + subject + "?=",
    "Content-Type: multipart/alternative; boundary=" + boundary,
    "",
    "--" + boundary,
    "Content-Type: text/plain; charset=UTF-8",
    "",
    body,
    "",
    "--" + boundary,
    "Content-Type: text/html; charset=UTF-8",
    "Content-Transfer-Encoding: base64",
    "",
    body,
    "",
    "--" + boundary,
  ].join("\r\n");
  return mailData;
}

function makeApiDraft() {
  const subject = "Hello MimeText World";
  const body = 'This is a plain text message';
  const me = Session.getActiveUser().getEmail();
  const raw = convert('test@test.com', me, subject, body);
  const b64 = Utilities.base64EncodeWebSafe(raw);
  console.log(raw)
  Gmail.Users.Drafts.create({ message: { raw: raw } }, me);
}

这个解决方案没有问题,它有效。然而,如果你想超越这个例子,比如添加多个收件人、有不同的纯文本和 html 正文、管理附件等,你将不得不手动编写所有代码,这需要理解 RFC2822 MIMEText格式。

因此输入新的更简单的解决方案。

解决方案二:新的简便方法

我偶然发现 this library 生成用 Node.js 编写的 MIMEText 电子邮件。所以我认为完美。我分叉了 repo 并修改了一些东西使其成为 GAS-compatible,特别是:

  1. 使用 Utilities.base64Encode()Utilities.base64EncodeWebSafe()
  2. 完成 Base 64 编码
  3. 附加文件只需传递一个 GAS DriveApp.File object
  4. 我确保在需要的地方出现了正确的 MIMEText headers 和 base 64 编码。

虽然我的 pull request 未决,但我使用 Webpack 转译了整个内容(因为该库确实有依赖项)并将其作为 GAS 库发布在这个 ID 下:

1HzFRRghlhuCDl0FUnuE9uKAK39GfeuUJAE3oOsjv74Qjq1UW8YEboEit

这里有an example project大家可以用来测试一下,不过代码基本如下:

const testMimeText = () => {
  const { message } = MimeText;
  message.setSender({
    name: 'Dmitry Kostyuk',
    addr: 'dmitry.kostyuk@gmail.com',
  });
  const file = DriveApp.getFileById('1pdMwlGL1WZTbi-Q2-Fc7nBm-9NKphkKg');
  const me = Session.getActiveUser().getEmail();

  message.setRecipient('dmitry.kostyuk@gmail.com');
  message.setSubject('Hello MimeText World!');
  message.setMessage('This is a plain text message ' + getAllEmojis(), 'text/plain');
  message.setMessage('<p>This is an html message</p><p>' + getAllEmojis() + '</p>\r\n\r\n', 'text/html');
  message.setAttachments([file]);

  const raw = message.asEncoded();
  Gmail.Users.Drafts.create({ message: { raw: raw } }, me);
}

const getDriveAuth = () => DriveApp.getRootFolder();

我想我掉进了一个我从未想过的兔子洞,但我对结果很满意:)