Files
2026-02-12 14:28:07 -05:00

227 lines
8.0 KiB
JavaScript

/***************
* CONFIG
***************/
const CONFIG = {
SPREADSHEET_ID: "17II0V8EsTL-9nHq2HgqhHcay-t-hhOaY5wNEzXHEsAM",
SOURCE_SHEET_NAME: "WSMinMCA.csv", // tab name inside the spreadsheet
EMAIL_COLUMN_INDEX_1BASED: 3, // Column C = 3 (1-based)
SENDER: "nrasmussen@gainsight.com",
SUBJECT: "DATA REQUEST",
PROCESSED_LABEL: "DATA_REQUEST_PROCESSED",
TEMP_SHEET_PREFIX: "DATA_REQUEST_", // new tab prefix
MAX_THREADS_PER_RUN: 10 // safety throttle
};
/**
* Run once to create the time-driven trigger.
* (There is no true "email received" trigger for Gmail in Apps Script.)
*/
function setupTrigger() {
// Optional: delete existing triggers for this function to avoid duplicates
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === "processDataRequests")
.forEach(t => ScriptApp.deleteTrigger(t));
// Create a trigger every 5 minutes (adjust as desired)
ScriptApp.newTrigger("processDataRequests")
.timeBased()
.everyMinutes(5)
.create();
}
/**
* Main worker: finds unread "DATA REQUEST" messages from aubrey@ws.com,
* extracts target email from body, filters rows, creates new tab, CSVs it,
* replies with attachment, and labels thread as processed.
*/
function processDataRequests() {
// const label = GmailApp.getUserLabelByName(CONFIG.PROCESSED_LABEL) || GmailApp.createLabel(CONFIG.PROCESSED_LABEL);
// // Search unread matching threads that are not already labeled processed
// const query = [
// `from:${CONFIG.SENDER}`,
// `subject:"${CONFIG.SUBJECT}"`,
// "is:unread",
// `-label:${CONFIG.PROCESSED_LABEL}`
// ].join(" ");
const threads = GmailApp.search(query, 0, CONFIG.MAX_THREADS_PER_RUN);
Logger.log(threads);
// if (!threads.length) return;
const ss = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID);
const sourceSheet = ss.getSheetByName(CONFIG.SOURCE_SHEET_NAME);
if (!sourceSheet) throw new Error(`Sheet tab not found: ${CONFIG.SOURCE_SHEET_NAME}`);
// Read all data once for efficiency
const sourceValues = sourceSheet.getDataRange().getValues();
if (sourceValues.length < 1) throw new Error(`No data found in ${CONFIG.SOURCE_SHEET_NAME}`);
const header = sourceValues[0];
const emailColIdx0 = CONFIG.EMAIL_COLUMN_INDEX_1BASED - 1;
threads.forEach(thread => {
const messages = thread.getMessages();
// Process only unread messages in the thread (usually just the newest)
messages.forEach(message => {
if (!message.isUnread()) return;
try {
const plainBody = message.getPlainBody() || "";
const targetEmail = extractFirstNonSenderEmail_(plainBody, CONFIG.SENDER);
if (!targetEmail) {
message.reply(
`I received your DATA REQUEST, but I couldn't find a target email address in the message body.\n\n` +
`Please include an email like "someone@company.com" in the body and resend.`
);
message.markRead();
return;
}
const targetLower = targetEmail.toLowerCase();
// Filter rows where Column C exactly matches the extracted email (case-insensitive)
const matches = sourceValues
.slice(1) // skip header
.filter(row => String(row[emailColIdx0] || "").trim().toLowerCase() === targetLower);
// Create a new tab with results
const newSheetName = makeSafeSheetName_(
`${CONFIG.TEMP_SHEET_PREFIX}${targetLower}_${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd_HHmmss")}`
);
const resultSheet = ss.insertSheet(newSheetName);
resultSheet.getRange(1, 1, 1, header.length).setValues([header]);
if (matches.length) {
resultSheet.getRange(2, 1, matches.length, header.length).setValues(matches);
}
// Convert result tab to CSV
const csv = valuesToCsv_(resultSheet.getDataRange().getValues());
const blob = Utilities.newBlob(csv, "text/csv", `${targetLower}.csv`);
// Reply with CSV attached (keeps threading)
const body =
`Attached is the CSV export for ${targetEmail}.\n\n` +
`Rows matched (excluding header): ${matches.length}\n` +
`Source tab: ${CONFIG.SOURCE_SHEET_NAME}\n` +
`Result tab: ${newSheetName}\n`;
message.reply(body, { attachments: [blob] });
// Mark processed
message.markRead();
thread.addLabel(label);
// Optional cleanup: uncomment if you do NOT want to keep the result tab
// ss.deleteSheet(resultSheet);
} catch (err) {
// Fail gracefully: reply with error so the sender knows it didn't work
message.reply(
`I attempted to process this DATA REQUEST but hit an error:\n\n${err && err.stack ? err.stack : err}`
);
message.markRead();
}
});
});
}
function modifiedProcessDataRequests() {
const ss = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID);
const sourceSheet = ss.getSheetByName(CONFIG.SOURCE_SHEET_NAME);
if (!sourceSheet) throw new Error(`Sheet tab not found: ${CONFIG.SOURCE_SHEET_NAME}`);
const targetEmail = "khodge2@wsgc.com";
const targetLower = targetEmail.toLowerCase();
// Read all data once for efficiency
const sourceValues = sourceSheet.getDataRange().getValues();
if (sourceValues.length < 1) throw new Error(`No data found in ${CONFIG.SOURCE_SHEET_NAME}`);
const header = sourceValues[0];
Logger.log(header);
const emailColIdx0 = CONFIG.EMAIL_COLUMN_INDEX_1BASED - 1;
Logger.log(emailColIdx0);
// Filter rows where Column C exactly matches the extracted email (case-insensitive)
const matches = sourceValues
.slice(1) // skip header
.filter(row => String(row[emailColIdx0] || "").trim().toLowerCase() === targetLower);
// Create a new tab with results
const newSheetName = makeSafeSheetName_(
`${CONFIG.TEMP_SHEET_PREFIX}${targetLower}_${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd_HHmmss")}`
);
const resultSheet = ss.insertSheet(newSheetName);
resultSheet.getRange(1, 1, 1, header.length).setValues([header]);
if (matches.length) {
resultSheet.getRange(2, 1, matches.length, header.length).setValues(matches);
}
// Convert result tab to CSV
const csv = valuesToCsv_(resultSheet.getDataRange().getValues());
const blob = Utilities.newBlob(csv, "text/csv", `${targetLower}.csv`);
// Reply with CSV attached (keeps threading)
const body =
`Attached is the CSV export for ${targetEmail}.\n\n` +
`Rows matched (excluding header): ${matches.length}\n` +
`Source tab: ${CONFIG.SOURCE_SHEET_NAME}\n` +
`Result tab: ${newSheetName}\n`;
Logger.log(body);
// Optional cleanup: uncomment if you do NOT want to keep the result tab
// ss.deleteSheet(resultSheet);
}
/****************
* HELPERS
****************/
/**
* Extracts the first email address found in the text that is NOT the sender email.
*/
function extractFirstNonSenderEmail_(text, senderEmail) {
const re = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig;
const found = text.match(re) || [];
const senderLower = (senderEmail || "").toLowerCase();
const first = found
.map(e => e.trim())
.find(e => e.toLowerCase() !== senderLower);
return first || null;
}
/**
* Converts a 2D array of values to RFC-ish CSV:
* - Wrap fields in quotes if they contain comma, quote, or newline
* - Double internal quotes
*/
function valuesToCsv_(values) {
return values.map(row => row.map(cell => {
let s = cell === null || cell === undefined ? "" : String(cell);
// Normalize line endings inside cells
s = s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
const needsQuotes = /[",\n]/.test(s);
if (needsQuotes) s = `"${s.replace(/"/g, '""')}"`;
return s;
}).join(",")).join("\r\n");
}
/**
* Google Sheets tab names have limits; keep it safe.
*/
function makeSafeSheetName_(name) {
// Remove characters Sheets doesn't like: [ ] : * ? / \
let safe = name.replace(/[\[\]:*?/\\]/g, " ");
safe = safe.replace(/\s+/g, " ").trim();
// Max length 100
if (safe.length > 100) safe = safe.slice(0, 100);
return safe || "DATA_REQUEST_RESULT";
}