Gmail 到 Google 驱动器脚本损坏

Gmail to Google Drive script broken

我不确定 Google App Script 是否有最近的更新,但我已经使用这个脚本几个月了,现在它已经坏了,无法工作。

这是我正在使用的脚本:

Code.gs:

// Gmail2GDrive
// https://github.com/ahochsteger/gmail2gdrive

/**
 * Returns the label with the given name or creates it if not existing.
 */
function getOrCreateLabel(labelName) {
  var label = GmailApp.getUserLabelByName(labelName);
  if (label == null) {
    label = GmailApp.createLabel(labelName);
  }
  return label;
}

/**
 * Recursive function to create and return a complete folder path.
 */
function getOrCreateSubFolder(baseFolder,folderArray) {
  if (folderArray.length == 0) {
    return baseFolder;
  }
  var nextFolderName = folderArray.shift();
  var nextFolder = null;
  var folders = baseFolder.getFolders();
  while (folders.hasNext()) {
    var folder = folders.next();
    if (folder.getName() == nextFolderName) {
      nextFolder = folder;
      break;
    }
  }
  if (nextFolder == null) {
    // Folder does not exist - create it.
    nextFolder = baseFolder.createFolder(nextFolderName);
  }
  return getOrCreateSubFolder(nextFolder,folderArray);
}

/**
 * Returns the GDrive folder with the given path.
 */
function getFolderByPath(path) {
  var parts = path.split("/");

  if (parts[0] == '') parts.shift(); // Did path start at root, '/'?

  var folder = DriveApp.getRootFolder();
  for (var i = 0; i < parts.length; i++) {
    var result = folder.getFoldersByName(parts[i]);
    if (result.hasNext()) {
      folder = result.next();
    } else {
      throw new Error( "folder not found." );
    }
  }
  return folder;
}

/**
 * Returns the GDrive folder with the given name or creates it if not existing.
 */
function getOrCreateFolder(folderName) {
  var folder;
  try {
    folder = getFolderByPath(folderName);
  } catch(e) {
    var folderArray = folderName.split("/");
    folder = getOrCreateSubFolder(DriveApp.getRootFolder(), folderArray);
  }
  return folder;
}






/**
 * Processes a message
 */
function processMessage(message, rule, config) {
  Logger.log("INFO:       Processing message: "+message.getSubject() + " (" + message.getId() + ")");
  var messageDate = message.getDate();
  var attachments = message.getAttachments();

  for (var attIdx=0; attIdx<attachments.length; attIdx++) {
    var attachment = attachments[attIdx];
    var attachmentName = attachment.getName();

    Logger.log("INFO:         Processing attachment: "+attachment.getName());
    var match = true;
    if (rule.filenameFromRegexp) {
    var re = new RegExp(rule.filenameFromRegexp);
      match = (attachment.getName()).match(re);
    }
    if (!match) {
      Logger.log("INFO:           Rejecting file '" + attachment.getName() + " not matching" + rule.filenameFromRegexp);
      continue;
    }
    try {
      var folder = getOrCreateFolder(Utilities.formatDate(messageDate, config.timezone, rule.folder));


     /////////////////////////////////////////////////////////////////////////////////////////////

      // var file = folder.removeFile(attachment);
      // file.setContent(attachment);


      var fileName = attachment.getName();
      var f = folder.getFilesByName(fileName);
      var file = f.hasNext() ? f.next() : folder.createFile(attachment);

      // file.setContent(attachment);

      /////////////////////////////////////////////////////////////////////////////////////////////



      if (rule.filenameFrom && rule.filenameTo && rule.filenameFrom == file.getName()) {
        var newFilename = Utilities.formatDate(messageDate, config.timezone, rule.filenameTo.replace('%s',message.getSubject()));
        Logger.log("INFO:           Renaming matched file '" + file.getName() + "' -> '" + newFilename + "'");
        file.setName(newFilename);
      }
      else if (rule.filenameTo) {
        var newFilename = Utilities.formatDate(messageDate, config.timezone, rule.filenameTo.replace('%s',message.getSubject()));
        Logger.log("INFO:           Renaming '" + file.getName() + "' -> '" + newFilename + "'");
        file.setName(newFilename);
      }
      file.setDescription("Mail title: " + message.getSubject() + "\nMail date: " + message.getDate() + "\nMail link: https://mail.google.com/mail/u/0/#inbox/" + message.getId());
      Utilities.sleep(config.sleepTime);
    } catch (e) {
      Logger.log(e);
    }
  }
}

/**
 * Generate HTML code for one message of a thread.
 */
function processThreadToHtml(thread) {
  Logger.log("INFO:   Generating HTML code of thread '" + thread.getFirstMessageSubject() + "'");
  var messages = thread.getMessages();
  var html = "";
  for (var msgIdx=0; msgIdx<messages.length; msgIdx++) {
    var message = messages[msgIdx];
    html += "From: " + message.getFrom() + "<br />\n";
    html += "To: " + message.getTo() + "<br />\n";
    html += "Date: " + message.getDate() + "<br />\n";
    html += "Subject: " + message.getSubject() + "<br />\n";
    html += "<hr />\n";
    html += message.getBody() + "\n";
    html += "<hr />\n";
  }
  return html;
}

/**
* Generate a PDF document for the whole thread using HTML from .
 */
function processThreadToPdf(thread, rule, html) {
  Logger.log("INFO: Saving PDF copy of thread '" + thread.getFirstMessageSubject() + "'");
  var folder = getOrCreateFolder(rule.folder);
  var html = processThreadToHtml(thread);
  var blob = Utilities.newBlob(html, 'text/html');
  var pdf = folder.createFile(blob.getAs('application/pdf')).setName(thread.getFirstMessageSubject() + ".pdf");
  return pdf;
}

/**
 * Main function that processes Gmail attachments and stores them in Google Drive.
 * Use this as trigger function for periodic execution.
 */
function Gmail2GDrive() {
  if (!GmailApp) return; // Skip script execution if GMail is currently not available (yes this happens from time to time and triggers spam emails!)
  var config = getGmail2GDriveConfig();
  var label = getOrCreateLabel(config.processedLabel);
  var end, start;
  start = new Date(); // Start timer

  Logger.log("INFO: Starting mail attachment processing.");
  if (config.globalFilter===undefined) {
    config.globalFilter = "has:attachment -in:trash -in:drafts -in:spam";
  }

  // Iterate over all rules:
  for (var ruleIdx=0; ruleIdx<config.rules.length; ruleIdx++) {
    var rule = config.rules[ruleIdx];
    var gSearchExp  = config.globalFilter + " " + rule.filter + " -label:" + config.processedLabel;
    if (config.newerThan != "") {
      gSearchExp += " newer_than:" + config.newerThan;
    }
    var doArchive = rule.archive == true;
    var doPDF = rule.saveThreadPDF == true;

    // Process all threads matching the search expression:
    var threads = GmailApp.search(gSearchExp);
    Logger.log("INFO:   Processing rule: "+gSearchExp);
    for (var threadIdx=0; threadIdx<threads.length; threadIdx++) {
      var thread = threads[threadIdx];
      end = new Date();
      var runTime = (end.getTime() - start.getTime())/1000;
      Logger.log("INFO:     Processing thread: "+thread.getFirstMessageSubject() + " (runtime: " + runTime + "s/" + config.maxRuntime + "s)");
      if (runTime >= config.maxRuntime) {
        Logger.log("WARNING: Self terminating script after " + runTime + "s")
        return;
      }

      // Process all messages of a thread:
      var messages = thread.getMessages();
      for (var msgIdx=0; msgIdx<messages.length; msgIdx++) {
        var message = messages[msgIdx];
        processMessage(message, rule, config);
      }
      if (doPDF) { // Generate a PDF document of a thread:
        processThreadToPdf(thread, rule);
      }

      // Mark a thread as processed:
     thread.addLabel(label);

      if (doArchive) { // Archive a thread if required
        Logger.log("INFO:     Archiving thread '" + thread.getFirstMessageSubject() + "' ...");
        thread.moveToArchive();
      }
    }
  }
  end = new Date(); // Stop timer
  var runTime = (end.getTime() - start.getTime())/1000;
  Logger.log("INFO: Finished mail attachment processing after " + runTime + "s");
}

Config.gs:

/**
 * Configuration for Gmail2GDrive
 * See https://github.com/ahochsteger/gmail2gdrive/blob/master/README.md for a config reference
 */
function getGmail2GDriveConfig() {
  return {
    // Global filter
    "globalFilter": "-in:trash -in:drafts -in:spam",
    // Gmail label for processed threads (will be created, if not existing):
    "processedLabel": "to-gdrive/processed",
    // Sleep time in milli seconds between processed messages:
    "sleepTime": 100,
    // Maximum script runtime in seconds (google scripts will be killed after 5 minutes):
    "maxRuntime": 45,
    // Only process message newer than (leave empty for no restriction; use d, m and y for day, month and year):
    "newerThan": "1m",
    // Timezone for date/time operations:
    "timezone": "GMT",

    // Processing rules:
    "rules": [
      /* { // Store all attachments sent to my.name+scans@gmail.com to the folder "Scans"
        "filter": "has:attachment to:my.name+scans@gmail.com",
        "folder": "'Scans'-yyyy-MM-dd"
      },
      { // Store all attachments from example1@example.com to the folder "Examples/example1"
        "filter": "has:attachment from:example1@example.com",
        "folder": "'Examples/example1'"
      }, */


      { // Store all pdf attachments from example2@example.com to the folder "Examples/example2"
        "filter": "label:gmail2drive",
        "folder": "'Swann'",
        "filenameFromRegexp": ".*\.jpg$",
        "archive": true
      },


      // { // Store all attachments from example3a@example.com OR from:example3b@example.com
        // to the folder "Examples/example3ab" while renaming all attachments to the pattern
        // defined in 'filenameTo' and archive the thread.
        // "filter": "has:attachment (from:example3a@example.com OR from:example3b@example.com)",
        // "folder": "'Examples/example3ab'",
        // "filenameTo": "'file-'yyyy-MM-dd-'%s.txt'",
        // "archive": true
      // },

      /* {
        // Store threads marked with label "PDF" in the folder "PDF Emails" als PDF document.
        "filter": "label:PDF",
        "saveThreadPDF": true,
        "folder": "PDF Emails"
      },
      { // Store all attachments named "file.txt" from example4@example.com to the
        // folder "Examples/example4" and rename the attachment to the pattern
        // defined in 'filenameTo' and archive the thread.
        "filter": "has:attachment from:example4@example.com",
        "folder": "'Examples/example4'",
        "filenameFrom": "file.txt",
        "filenameTo": "'file-'yyyy-MM-dd-'%s.txt'"
      } */

    ]
  };
}

基本上,脚本会检查带有标签 "gmail2drive" 的电子邮件,如果存在,则提取电子邮件中的附件并将其上传到我的 Google 中名为 "Swann" 的文件夹中驾驶。然后它将标签 "to-gdrive/processed" 应用于已处理的电子邮件,因此它们不会再次处理。

有时,某些附件可能会被提取两次,从而产生重复。所以脚本也会检查重复项,希望能防止这种情况发生。

所以这在大多数情况下都运行良好,但最近它坏了,导致多次提取相同的附件以及多次处理相同的电子邮件的问题。这就像脚本忽略了标签 "to-gdrive/processed" 或其他东西。

我试过使用不同的标签,结果是一样的。

我还应该澄清一下,我不是程序员或编写脚本的人。我对如何在 Google 脚本中进行设置知之甚少。我可以按照一般说明行事。我希望知道如何阅读脚本的人能够解决此问题并让我知道要更改的内容。

提前致谢。

经过多次评论和 chat 对话后,我们推断此问题是由于 DVR 新电子邮件被视为线程的回复而产生的。这弄乱了标签并导致重复上传到云端硬盘文件夹。

新脚本:

假设收件箱中有一条规则将标签 "gmail2drive" 附加到从 DVR 接收的线程,这将获取所有具有该标签的线程,处理每个线程的每条消息并重新发送它作为新邮件。它还使用日期(包括毫秒)更改主题,因此它确保每封新电子邮件都有不同的主题并且相互独立。 处理完所有消息后,将线程发送到Bin。

function OrganiseEmails() {

  var threads = GmailApp.search("-in:trash -in:drafts -in:spam label:gmail2drive -label:to-gdrive-processed")

  for (i in threads){
    var messages = threads[i].getMessages();

    for (j in messages){
      if (messages[j].getAttachments().length > 0){  
        var to = messages[j].getTo();
        var date = Utilities.formatDate(new Date(), "GMT","yyyy-MM-dd' at 'HH:mm:ss:SS' '");
        var subject = "DVR motion detected - " + date;
        var body = "test";
        var attachment = messages[j].getAttachments()[0];

        var options = {
          attachments: attachment
        }
        GmailApp.sendEmail(to, subject, body, options);
      }      
    }
    var rem_label = GmailApp.getUserLabelByName("gmail2drive"); 
    rem_label.removeFromThread(threads[i]);
    threads[i].moveToTrash();
  }  
}

我对 Gmail2Drive 和 Config.gs 脚本做了一些更改,因为现在它不会按标签而是按主题进行过滤:

Code.gs

  1. 删除了函数getOrCreateLabel()。我们没有在此脚本中使用过滤器,因此没有必要。
  2. 出于显而易见的原因删除了行 var label = getOrCreateLabel(config.processedLabel);
  3. gSearchExp 变量声明中删除了 + " -label:" + config.processedLabel
  4. 将行 thread.addLabel(label); 更改为 thread.moveToTrash();

Config.gs

  1. 已将 in:inbox 添加到 globalFilter 搜索参数以避免再次处理线程,因为它们现在也在已发送中。
  2. 删除了行"processedLabel": "to-gdrive/processed",
  3. "filter""label:gmail2drive" 更改为 "subject:'DVR motion detected - '"

编辑

最后我们决定把两个代码放在一起,所以在 code.gs 中创建一个新函数并首先在 Gmail2Drive 函数中调用它。此外,您可以永久删除电子邮件,而不是更改标签 Gmail.Users.Threads.remove("me", threadid); 发送电子邮件后。