解析gmail并追加到Google表格中会返回未定义的错误(应用脚本)

时间:2019-04-20 17:50:24

标签: google-apps-script google-sheets gmail

我正在尝试执行我在github上找到的脚本,该脚本可以解析电子邮件并将其从gmail帐户追加到Google工作表中。

https://github.com/jimi985/RSVP-Google-App-Script

我运行AddRSVPToSpreadsheet的错误是

TypeError:无法调用未定义的方法“ toArray”。 (第154行,文件“代码”)

我正在逐步运行这些功能,并且已经通过电子邮件发送了示例文本,添加了gsheets字符串,并允许脚本访问电子邮件帐户。

电子邮件标签也是;婚礼回复副总裁

是我运行这些功能的顺序吗?

RSVP 初始化电子表格 处理器vps addrsvpstospreadsheet

有特殊命令吗? 当我选择运行功能时,此顺序会在应用脚本界面中列出。

有人能指出我正确的方向吗?或解释为什么我会收到此错误?

/*
 *
 * Script that pulls data in from Gmail with a specific label.
 * All RSVP emails for my wedding are automatically filtered to this label.
 * Then the emails are parsed and appended to the spreadhseet if they aren't already in the sheet.
 *
 * Example Email:

From: John Doe & Jane Doe <jdoe123@gmail.com>
Subject: John Doe & Jane Doe Fitzgerald has RSVP
Name: John Doe & Jane Doe
Email: jdoe123@gmail.com
Number of Guest(s): 1
Events:
Can Attend: No
 *
 */


/*
 * Global Variables
 */
var LOGGING_ENABLED = true;

//Set a start date to filter out older emails
// var date = new Date('April 20, 2019 18:30:00');
// var START_TIME = date.getTime();
var START_DATE = new Date('April 20, 2019 18:30:00');
var MAX_THREADS_TO_PROCESS = 0;

//Set which label we are using to filter which emails are being retrieved
var RSVP_LABEL = 'Wedding RSVPs';
var SPREADSHEET_ID = 'example2403tehewreotherer';

var spreadsheet_field_names = [];
var sheet;

if(LOGGING_ENABLED){
    //Clear out previous logs
    Logger.clear();
    log("Starting script: Parse Wedding RSVPs");
}

/*
 * RSVP class that stores the details of the rsvp
 * Also has a toArray method that is useful for passing the data
 * into a Gmail Spreadsheet.
 */
function RSVP(){

    this.property_order = ['date', 'name', 'email', 'number_of_guests', 'events', 'can_attend'];

    this.toArray = function(){
        var arr = [];
        for(i = 0; i < this.property_order.length; i++){
            if(this.hasOwnProperty(this.property_order[i])){
                arr.push(this[this.property_order[i]]);
            }
        }
        return arr;
    }

}

/*
 * Loads the spreadsheet based on the predefined spreadsheet,
 * Then populates the list of header fields within the spreadsheet.
 * @returns - {boolean} - true if successful, false on failure.
 */
function initializeSpreadsheet(){

    var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);

    if(!spreadsheet){
        return false;
    }

    sheet = spreadsheet.getSheets()[0];

    if(!sheet){
        return false;
    }

    spreadsheet_field_names = getSpreadsheetFieldNames(sheet);

    if(!spreadsheet_field_names || spreadsheet_field_names.length <= 0){
        return false;
    }

    return true;

}

/*
 * Function that retrieves a list of gmail email threads
 * with a provided label and parses them.
 * @param - {string} rsvp_label - The label applied to emails that should be parsed.
 */
function processRSVPs(rsvp_label) {

    var rsvp_threads = getGmailThreadsWithLabelName(rsvp_label);

    if(rsvp_threads.length <= 0){
        log("No Wedding RSVP Threads found");
        return false;
    }

    //We found some emails to parse, open the spreadsheet
    if(!initializeSpreadsheet()){
        log("Failed to initialize spreadsheet.");
        return false;
    }

    messages = [];
    for(var i = 0; i < rsvp_threads.length; i++) {

        messages = messages.concat(rsvp_threads[i].getMessages());

        //While debugging, only try to process the first few email threads
        if(MAX_THREADS_TO_PROCESS > 0 && (i + 1) >= MAX_THREADS_TO_PROCESS){
            break;
        }

    }

    //Filter out messages we don't want to parse (based on date or other criteria)
    messages = filterMessages(messages);

    for(i = 0; i < messages.length; i++){

        var rsvp = parseRSVPEmail(messages[i].getBody());
        rsvp.date = formatDate(messages[i].getDate());

        //Add rsvp entry to spreadsheet
        if(addRSVPToSpreadsheet(rsvp)) {
            log("RSVP Successfully added to spreadsheet: " + JSON.stringify(rsvp));
        }else{
            log("RSVP could not be added to spreadsheet: " + JSON.stringify(rsvp));
        }

    }

}

/*
 * Loops through an array of email threads and parses each thread.
 * @param - {Object} rsvp - the rsvp object to add to the spreadsheet.
 * @returns - {boolean} - true if added to the spreadsheet,
 *  false if already exists or there was an error.
 */
function addRSVPToSpreadsheet(rsvp){

    //Convert the rsvp object into an array
    rsvp_array = rsvp.toArray();    

    if(RSVPEntryExists(rsvp, sheet)){
        log('Entry already found in spreadsheet: ' + JSON.stringify(rsvp));
        return false;
    }

    sheet = sheet.appendRow(rsvp_array);

    return true;

}

/*
 * Returns the list of field names from the first row of a spreadsheet.
 * @param {Sheet} sheet - Gmail Sheet object.
 * @param {string[]} - array of field names.
 */
function getSpreadsheetFieldNames(sheet){

    //Find the column index of the last row
    var last_column = sheet.getLastColumn();

    //Sheet.getRange(row, column, numRows, numColumns)
    //Get all of the values from the first row
    var range = sheet.getRange(1, 1, 1, last_column);
    var values = range.getValues();

    var header = [];
    for (var row in values) {
        for (var col in values[row]) {
            header_column_name = values[row][col].toLowerCase();
            header_column_name = replaceAll(' ', '_', header_column_name);
            header_column_name = replaceAll('[^a-z0-9_]', '', header_column_name);
            header.push(header_column_name);
        }
    }

    return header;

}

/*
 * Checks if the RSVP entry already exists in the Spreadsheet.
 * @param {RSVP} rsvp - RSVP object to be checked.
 * @param {Sheet} sheet - Gmail Sheet to be checked.
 * @returns {boolean} - true if the entry exists or if there was an error, otherwise false.
 */
function RSVPEntryExists(rsvp, sheet){

    //Find the column that we need to be searching
    search_column = spreadsheet_field_names.indexOf('name');

    if(search_column == -1){
        //Flag the entry as existing since there was an error.

        return true;
    }

    var last_row = sheet.getLastRow();

    // getRange(row, column, numRows)
    //Get all of the values from the search column for all rows
    //(Add 1 to the search_column as the Spreadsheet is 1-indexed instead of 0-indexed)
    //Start on row 2 as we don't want the header row
    var range = sheet.getRange(2, search_column + 1, last_row);

    var values = range.getValues();

    for (var row in values) {
        for (var col in values[row]) {
            if(values[row][col] == rsvp.name){
                return true;
            }
        }
    }

    return false;

}

/*
 * Filters an array of messages and returns a modified version of the original array
 * @param - {GmailMessage[]} messages - list of messages to be filtered.
 * @returns - {GmailMessage[]} - filtered list of messages.
 */
function filterMessages(messages) {

    if(messages.length <= 0){
        return false;
    }

     // The messages need to be traversed in reverse order
     // This is because we need to call the splice method to remove an element from
     // the messages array which reindexes the array
    for (var i = messages.length - 1; i >= 0; i--) {

        //Filter out messages if they are older than our start date.
        if(isMessageOlderThanDate(messages[i], START_DATE)) {
            messages.splice(i, 1);
            continue;
        }

    }

    return messages;

}

/*
 * Returns true if a message occurred after a specified date, otherwise false.
 * This allows emails to be skipped that have already been processed.
 * @param {GmailMessage} message - The GmailMessage object to be checked.
 * @returns {boolean}
 */
function isMessageOlderThanDate(message, start_date) {
    return (message.getDate().getTime() > start_date.getTime()) ? false : true;
}

/*
 * Parses an email of a specific format to retrieve its component parts
 * The email must be formatted with as: <string>: <string>, representing a key/value pair.
 * @param - {string} body - the contents of an rsvp email.
 * @returns - {Object} - rsvp details object.
 */
function parseRSVPEmail(body) {

    body = replaceAll('<br />', '', body);

    var message_lines = body.split("\n");

    //Initialize our RSVP Object
    rsvp = new RSVP();

    var skip_fields = ['from', 'subject']

    for(i = 0; i < message_lines.length; i++) {

        var line_parts = message_lines[i].split(':');

        //Make the line type lower case, replace spaces with underscores, and remove any other unexpected character
        // var line_type = line_parts[0].toLowerCase().replaceAll(' ', '_').replaceAll('/[^a-z0-9_]/', '');
        var line_type = line_parts[0].toLowerCase();
        line_type = replaceAll(' ', '_', line_type);
        line_type = replaceAll('[^a-z0-9_]', '', line_type);

        //Skip over fields we don't need
        if(skip_fields.indexOf(line_type) != -1){
            continue;
        }

        //In case there's a ":" in the message, reassemble the rest of the line
        var line_value = "";

        for(var j = 1; j < line_parts.length; j++) {
            line_value += line_parts[j];
        }

        line_value = line_value.trim();

        rsvp[line_type] = line_value;

        if(line_type == 'can_attend'){
            //can_attend is the last line of the email that needs to be parsed
            //Break out of all loops or return
            break;
        }

    }

    return rsvp;

}

/*
 * Returns an array of GmailThreads that match a provided label.
 * @param {string} label_name - Gmail Label name being searched for.
 */
function getGmailThreadsWithLabelName(label_name) {

    var threads = [];
    var new_threads = [];
    var start = 0;
    var loop_count = 30;

    //First see if we can find the label
    var label = GmailApp.getUserLabelByName(label_name);

    //We could not retrieve a label, fail out.
    if(!label){
        return threads;
    }

    do {
        new_threads = label.getThreads(start,loop_count);
        threads = threads.concat(new_threads);
        start += loop_count;
    }
    while(new_threads.length > 0);

    return threads;

}

/*
 * Additional String method expanding on the existing replace method,
 * allowing all matches of a regular expression to be replaced.
 * @param {string} find - Regular expression string
 * @param {string} replace - String to replace the matches that are found.
 */
// String.prototype.replaceAll = function(find, replace) {
//  return this.replace(new RegExp(escapeRegExp(find), 'g'), replace);
// }

function replaceAll(find, replace, subject){
    return subject.replace(new RegExp(find, 'g'), replace);
}

/*
 * Helper function that escapes a regular expression provided as a string
 * @param {string} regex - Regular expression string.
 * This escape does not work if you are trying to use metacharacters in your regular expression.
 */
function escapeRegExp(regex) {
    return regex.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

/*
 * Helper function to format a javascript datetime value to be stored in a spreadsheet
 * @param {Date} date - Javascript date object to be formatted
 * @returns {string} - date formatted as "MM/DD/YYYY HH:MM:SS"
 */
function formatDate(date) {
    var formatted_date = (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear();
    var formatted_time = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
    return formatted_date + ' ' + formatted_time;
}

/*
 * Custom logging function that checks if logging has been enabled
 * @param {String} message - message to be logged.
 */
function log(message){
    if(LOGGING_ENABLED){
        Logger.log(message);
    }
}

processRSVPs(RSVP_LABEL);

1 个答案:

答案 0 :(得分:0)

您无法从编辑器的运行菜单中运行功能addRSVPToSpreadsheet,因为它需要参数rsvp。您收到的错误是因为运行菜单尝试运行addRSVPToSpreadsheet(),即没有rsvp参数。为rsvp分配了值undefined,因此undefined.toArray()会导致您看到错误。

运行菜单中的顺序仅是功能在当前脚本文件中出现的顺序。这并不意味着您应该按此顺序运行它们。

脚本的入口点似乎是最后一行

processRSVPs(RSVP_LABEL);

如果要在运行菜单中调用该行,则需要添加驱动程序功能,例如

function startProcess() {
  processRSVPs(RSVP_LABEL);
}