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

Posted

技术标签:

【中文标题】在 Google Apps 脚本中使用高级 Gmail 服务创建与表情符号兼容的 Gmail 草稿【英文标题】:Creating an Emoji-Compatible Gmail draft with Advanced Gmail Service in Google Apps Script 【发布时间】:2021-11-28 09:24:33 【问题描述】:

我正在尝试使用 Gmail 高级服务create a Gmail draft。我需要的是让body 在bytes format 中包含data。我构建了以下功能:

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:对 gmail.users.drafts.c​​reate 的 API 调用失败并出现错误:收到的 JSON 负载无效。 'draft.message.payload.parts[0].body' 中的未知名称“数据”:原始字段不重复,无法启动列表。

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

我在这里错过了什么?

更新

我将字节字符串格式化为数组的原因是,当您阅读草稿时,这就是 Gmail API 返回的内容。如果您有不同的工作代码,我会全力以赴。而且我不能使用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"

【问题讨论】:

> base64 编码的字符串。 应该是string。我认为您不应该使用payload,而应该使用message:raw:"b64string" 见python guide。你需要raw。创建没有库的 MIMEText 有点困难,但可行。我认为@Tanaike 对创建符合 RFC2822 的 MIMEText 有一些答案 给你go @TheMaster 谢谢,我能够在引用的答案中调整高级 Gmail 服务部分,以使事情对我有用。我会按照你的建议尽快写一个答案。 *** 似乎是唯一记录此行为的地方。我现在也必须弄清楚附件,但这是一个不同的问题 【参考方案1】:

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

    什么不起作用 解决方案一:艰难的道路 解决方案二:新的简单方法

什么没用

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

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

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

解决方案一:艰难的道路

结合here 和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 兼容,特别是:

    Base 64 编码使用Utilities.base64Encode()Utilities.base64EncodeWebSafe() 完成 只需传递 GAS DriveApp.File 对象即可完成附加文件 我确保正确的 MIMEText 标头和 base 64 编码存在于需要它们的位置。

当我的拉取请求处于待处理状态时,我使用 Webpack 转译了整个内容(因为该库确实具有依赖项)并将其发布为该 ID 下的 GAS 库:

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();

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

【讨论】:

以上是关于在 Google Apps 脚本中使用高级 Gmail 服务创建与表情符号兼容的 Gmail 草稿的主要内容,如果未能解决你的问题,请参考以下文章

是否可以使用 Apps 脚本运行 Google 表格插件?

在 Google Apps 脚本中使用 Mandrill API

在 Google Apps 脚本中使用 BigQuery 连接到 Google 电子表格

Google 表单 - 使用 Google Apps 脚本在项目中添加自定义按钮“更多信息”

如何在 Google Apps 脚本中使用服务帐户对 Google 表格进行身份验证

使用 Google Apps 脚本在 Blogger 中创建帖子