需要帮助优化标记电子邮件的谷歌应用脚​​本 [英] Need help optimizing a google apps script that labels emails

查看:21
本文介绍了需要帮助优化标记电子邮件的谷歌应用脚​​本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Gmail 存在一个问题,即会话标签不会应用于会话线程中到达的新邮件.这里的问题详情

Gmail has a issue where conversation labels are not applied to new messages that arrive in the conversation thread. issue details here

我们找到了 一个 Google Apps 脚本,用于修复 Gmail 收件箱中单个邮件的标签以解决此问题.脚本如下:

We found a Google Apps Script that fixes the labels on individual messages in the Gmail Inbox to address this issue. The script is as follows:

function relabeller() {
  var labels = GmailApp.getUserLabels();


  for (var i = 0; i < labels.length; i++) {
    Logger.log("label: " + i + " " + labels[i].getName());

    var threads = labels[i].getThreads(0,100);
    for (var j = 1; threads.length > 0; j++) {
      Logger.log( (j - 1) * 100 + threads.length);
      labels[i].addToThreads(threads);
      threads = labels[i].getThreads(j*100, 100);
    }
  }
}

但是,由于 Google Apps 脚本的 5 分钟执行时间限制,此脚本在包含超过 20,000 封邮件的电子邮件箱中超时.

However this script times out on email boxes with more than 20,000 messages due to the 5 mins execution time limit on Google Apps Script.

谁能建议一种优化此脚本的方法,使其不会超时?

Can anyone please suggest a way to optimize this script so that it doesn't timeout?

推荐答案

好的,我已经为此工作了几天,因为我对 Gmail 在对话中标记/不标记邮件的奇怪方式感到非常沮丧.

OK, I've been working on this for a few days because I was really frustrated with the strange way that Gmail labels/doesn't label messages in conversations.

实际上,标签不会自动应用于对话中的新消息,这让我大吃一惊.这根本没有反映在 Gmail 用户界面中.无法查看线程并确定标签仅适用于线程中的某些消息,并且您无法将标签添加到 UI 中的单个消息.在我处理下面的脚本时,我注意到您甚至无法以编程方式将标签添加到单个消息中.因此,目前的行为确实没有理由.

I'm flabbergasted actually that labels aren't automatically applied to new messages in a conversation. This is not reflected at all in the Gmail UI. There's no way to look at a thread and determine that the labels only apply to some messages in the thread, and you cannot add labels to a single message in the UI. As I was working through my script below, I noticed that you can't even programmatically add labels to a single message. So there really is no reason for the current behavior.

我的咆哮已经结束,我对脚本有一些注意事项.

With my rant out of the way, I have a few notes about the script.

  1. 我将 Saqib 的代码与 Serge 的代码结合在一起.
  2. 该脚本有两部分:一个是重新标记所有附加了用户标签的线程的初始运行,另一个是标记最近的电子邮件的维护运行(目前回顾 4 天).在单次运行期间只执行一个部分.初始运行完成后,将仅运行维护部分.您可以设置一个触发器,使其每天运行一次,或多或少地运行一次,具体取决于您的需要.
  3. 初始运行在 4 分钟后停止,以避免在 5 分钟脚本时间限制内终止.它将触发器设置为在 4 分钟后再次运行(这两个时间都可以使用脚本中的常量进行更改).触发器在下次运行时被删除.
    • 维护部分没有运行时检查.如果您在过去 4 天内有大量电子邮件,则维护部分可能会达到脚本时间限制.我可能可以在这里更改脚本以提高效率,但到目前为止它对我有用,所以我并没有真正的动力去改进它.

我用它在大约半小时(包括等待时间)内处理了 20,000 封电子邮件.我实际上运行了两次,所以它在一天内处理了 40,000 封电子邮件.我猜 10,000 的 Gmail 读/写限制不是这里应用的(也许一次将标签应用于 100 个线程算作单个写入事件而不是 100?).根据它发送的状态电子邮件,它在 4 分钟的运行中通过了大约 5,000 个线程.

I have used this to process 20,000 emails in around half an hour (including wait times). I actually ran it twice, so it processed 40,000 emails in one day. I guess the Gmail read/write limit of 10,000 isn't what is being applied here (maybe applying a label to 100 threads at a time counts as a single write event instead of 100?). It gets through about 5,000 threads in a 4 minute run, according to the status email it sends.

很抱歉排长队.我责怪宽屏显示器.让我知道你的想法!

Sorry for the long lines. I blame the widescreen monitors. Let me know what you think!

function relabelGmail() {

  var startTime= (new Date()).getTime(); // Time at start of script
  var BATCH=100; // total number of threads to apply label to at once.
  var LOOKBACKDAYS=4; // Days to look back for maintenance section of script. Should be at least 2
  var MAX_RUN_TIME=4*60*1000; // Time in ms for max execution. 4 minutes is a good start.
  var WAIT_TIME=4*60*1000; // Time in ms to wait before starting the script again.
  Logger.clear();



//  ScriptProperties.deleteAllProperties(); return; // Uncomment this line and run once to start over completely

  if(ScriptProperties.getKeys().length==0){ // this is to create keys on the first run
    ScriptProperties.setProperties({'itemsProcessed':0, 'initFinished':false, 'lastrun':'20000101', 'itemsProcessedToday':0, 
                                    'currentLabel':'null-label-NOTREAL', 'currentLabelStart':0, 'autoTrig':0, 'autoTrigID':'0'});
  }

  var itemsP = Number(ScriptProperties.getProperty('itemsProcessed')); // total counter
  var initTemp = ScriptProperties.getProperty('initFinished'); // keeps track of when initial run is finished. 
  var initF = (initTemp.toLowerCase() == 'true'); // Make it boolean

  var lastR = ScriptProperties.getProperty('lastrun'); // String of date corresponding to itemsProcessedToday in format yyyymmdd
  var itemsPT = Number(ScriptProperties.getProperty('itemsProcessedToday')); // daily counter
  var currentL = ScriptProperties.getProperty('currentLabel'); // Label currently being processed
  var currentLS = Number(ScriptProperties.getProperty('currentLabelStart')); // Thread number to start on

  var autoT = Number(ScriptProperties.getProperty('autoTrig')); // Number to say whether the last run made an automatic trigger
  var autoTID = ScriptProperties.getProperty('autoTrigID'); // Unique ID of last written auto trigger

  // First thing: google terminates scripts after 5 minutes. 
  // If 4 minutes have passed, this script will terminate, write some data, 
  // and create a trigger to re-schedule itself to start again in a few minutes. 
  // If an auto trigger was created last run, it is deleted here.
  if (autoT) {
    var allTriggers = ScriptApp.getProjectTriggers();

    // Loop over all triggers. If trigger isn't found, then it must have ben deleted.
    for(var i=0; i < allTriggers.length; i++) {
      if (allTriggers[i].getUniqueId() == autoTID) {
        // Found the trigger and now delete it
        ScriptApp.deleteTrigger(allTriggers[i]);
        break;
      }
    }
    autoT = 0;
    autoTID = '0';
  }

  var today = dateToStr_();
  if (today == lastR) { // If new day, reset daily counter
    // Don't do anything
  } else {
    itemsPT = 0;
  }

  if (!initF) { // Don't do any of this if the initial run has been completed
    var labels = GmailApp.getUserLabels();

    // Find position of last label attempted
    var curLnum=0;
    for ( ; curLnum < labels.length; curLnum++) { 
      if (labels[curLnum].getName() == currentL) {break};
    }
    if (curLnum == labels.length) { // If label isn't found, start over at the beginning
      curLnum = 0;
      currentLS = 0;
      itemsP=0;
      currentL=labels[0].getName();
    }

    // Now start working through the labels until the quota is hit.
    // Use a try/catch to stop execution if your quota has been hit. 
    // Google can actually automatically email you, but we need to clean up a bit before terminating the script so it can properly pick up again tomorrow.
    try {
      for (var i = curLnum; i < labels.length; i++) {
        currentL = labels[i].getName(); // Next label
        Logger.log('label: ' + i + ' ' + currentL);

        var threads = labels[i].getThreads(currentLS,BATCH);

        for (var j = Math.floor(currentLS/BATCH); threads.length > 0; j++) {
          var currTime = (new Date()).getTime();
          if (currTime-startTime > MAX_RUN_TIME) {

            // Make the auto-trigger
            autoT = 1; // So the auto trigger gets deleted next time.

            var autoTrigger = ScriptApp.newTrigger('relabelGmail')
            .timeBased()
            .at(new Date(currTime+WAIT_TIME))
            .create();

            autoTID = autoTrigger.getUniqueId();

            // Now write all the values.
            ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                            'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});

            // Send an email
            var emailAddress = Session.getActiveUser().getEmail();
            GmailApp.sendEmail(emailAddress, 'Relabel job in progress', 'Your Gmail Relabeller has halted to avoid termination due to excess ' +
                               'run time. It will run again in ' + WAIT_TIME/1000/60 + ' minutes.

' + itemsP + ' threads have been processed. ' + itemsPT + 
                               ' have been processed today.

See the log below for more information:

' + Logger.getLog());
            return;
          } else {
            // keep on going
            var len = threads.length;
            Logger.log( j * BATCH + len);

            labels[i].addToThreads(threads);

            currentLS = currentLS + len;
            itemsP = itemsP + len;
            itemsPT = itemsPT + len;
            threads = labels[i].getThreads( (j+1) * BATCH, BATCH);
          }
        }

        currentLS = 0; // Reset LS counter
      }

      initF = true; // Initial run is done

    } catch (e) { // Clean up and send off a notice. 
      // Write current values back to ScriptProperties
      ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                      'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});

      var emailAddress = Session.getActiveUser().getEmail();
      var errorDate = new Date();
      GmailApp.sendEmail(emailAddress, 'Error "' + e.name + '" in Google Apps Script', 'Your Gmail Relabeller has failed in the following stack:

' + 
                         e.stack + '
This may be due to reaching your daily Gmail read/write quota. 
The error message is: ' + 
                         e.message + '
The error occurred at the following date and time: ' + errorDate + '

Thus far, ' + 
                         itemsP + ' threads have been processed. ' + itemsPT + ' have been processed today. 
See the log below for more information:' + 
                         '

' + Logger.getLog());
      return;
    }

    // Write current values back to ScriptProperties. Send completion email.
    ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                    'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigNumber':autoTID});

    var emailAddress = Session.getActiveUser().getEmail();
    GmailApp.sendEmail(emailAddress, 'Relabel job completed', 'Your Gmail Relabeller has finished its initial run.
' + 
                       'If you continue to run the script, it will skip the initial run and instead relabel ' + 
                       'all emails from the previous ' + LOOKBACKDAYS + ' days.

' + itemsP + ' threads were processed. ' + itemsPT + 
                       ' were processed today. 
See the log below for more information:' + '

' + Logger.getLog());

    return; // Don't run the maintenance section after initial run finish

  } // End initial run section statement


  // Below is the 'maintenance' section that will be run when the initial run is finished. It finds all new threads
  // (as defined by LOOKBACKDAYS) and applies any existing labels to all messages in each thread. Note that this 
  // won't miss older threads that are labeled by the user because all messages in a thread get the label
  // when the label action is first performed. If another message is then sent or received in that thread, 
  // then this maintenance section will find it because it will be deemed a "new" thread at that point. 
  // You may need to search further back the first time you run this if it took more than 3 days to finish
  // the initial run. For general maintenance, though, 4 days should be plenty.

  // Note that I have not implemented a script-run-time check for this section. 

  var threads = GmailApp.search('newer_than:' + LOOKBACKDAYS + 'd', 0, BATCH); // 
  var len = threads.length;

  for (var i=0; len > 0; i++) {

    for (var t = 0; t < len; t++) {
      var labels = threads[t].getLabels();

      for (var l = 0; l < labels.length; l++) { // Add each label to the thread
        labels[l].addToThread(threads[t]);
      }
    }

    itemsP = itemsP + len;
    itemsPT = itemsPT + len;

    threads = GmailApp.search('newer_than:' + LOOKBACKDAYS + 'd', (i+1) * BATCH, BATCH); 
    len = threads.length;
  }
  // Write the property data
  ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                  'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});
}


// Takes a date object and turns it into a string of form yyyymmdd
function dateToStr_(dateObj) { //takes in a date object, but uses current date if not a date

  if (!(dateObj instanceof Date)) {
    dateObj = new Date();
  }

  var dd = dateObj.getDate();
  var mm = dateObj.getMonth()+1; //January is 0!
  var yyyy = dateObj.getFullYear();

  if(dd<10){dd='0'+dd}; 
  if(mm<10){mm='0'+mm};
  dateStr = ''+yyyy+mm+dd;

  return dateStr;

}

2017 年 3 月 24 日我想我应该打开通知或其他东西,因为我从未见过 user29020 的问题.如果有人有同样的问题,我会这样做:我将其作为维护功能运行,方法是设置每日触发器,在每晚凌晨 1 点到 2 点之间运行.

3/24/2017 I guess I should turn on notifications or something, because I never saw the question from user29020. In case anyone ever has the same question, here's what I do: I run it as a maintenance function by setting a daily trigger to run each night between 1 and 2 AM.

附加说明:在过去一年左右的某个时间点,对 Gmail 的标记调用似乎已显着放缓.现在每个线程大约需要 0.2 秒,所以我预计最初运行 20k 封电子邮件至少需要运行 20 次左右才能完成.这也意味着,如果您通常每天收到超过 100-200 封电子邮件,维护部分也可能会开始花费太长时间并开始失败.现在有很多电子邮件,但我敢打赌,有些人会收到这么多,而且看起来你会比我第一次写失败时每天需要 1000 封左右的电子邮件更有可能收到.脚本.

An additional note: It seems that at some point in the last year or so, labeling calls to Gmail have slowed down significantly. It now takes around 0.2 seconds per thread, so I would expect an initial run of 20k emails to take at least 20 runs or so before it makes it all the way through. This also means that if you typically receive more than 100-200 emails a day, the maintenance section might also start to take too long and start to fail. Now that's a lot of emails, but I bet there are some people that receive that many, and it seems much more likely that you would hit that than the 1000 or so daily emails that would have been needed for failure back when I first wrote the script.

无论如何,一种缓解方法是将 LOOKBACKDAYS 减少到 4 以下,但我不建议将其设置为小于 2.

Anyway, one mitigation would be to reduce the LOOKBACKDAYS to less than 4, but I wouldn't recommend putting it less than 2.

这篇关于需要帮助优化标记电子邮件的谷歌应用脚​​本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆