Pulling Gmail "from:mailer-damon" and "from:postmaster" email data into google sheets? - email

I came across the code below in an article. I tried to modify it (q: "from:mailer-daemon OR from:postmaster",) so that the code can pull from not only mailer-daemon, but also postmaster emails. However, it handled the data differently for postmaster emails (postmaster recipient emails were put into the "Bounce Reason" column along with the reason). I'd also like for it to pull from a label I've created in Gmail called "Delete from CMMC List", but I'm unsure how to do that.
The column names that data is being separated into are as follows:
Bounce Date
Email Recipient
Error Status
Bounce Reason
Gmail Link
Original code that needs modification:
/*
* Gmail Bounce Report
* u/labnol Feb 12, 2020
* Written by Amit Agarwal
* email: amit#labnol.org
* twitter: u/labnol
* web: www.labnol.org
*/
const toast_ = e => SpreadsheetApp.getActiveSpreadsheet().toast(e);
const parseMessage_ = messageId => {
const message = GmailApp.getMessageById(messageId);
const body = message.getPlainBody();
const [, failAction] = body.match(/^Action:\s*(.+)/m) || [];
if (failAction === "failed") {
const emailAddress = message.getHeader("X-Failed-Recipients");
const [, errorStatus] = body.match(/^Status:\s*([.\d]+)/m) || [];
const [, , bounceReason] =
body.match(/^Diagnostic-Code:\s*(.+)\s*;\s*(.+)/m) || [];
if (errorStatus && bounceReason) {
return [
message.getDate(),
emailAddress,
errorStatus,
bounceReason.replace(/\s*(Please|Learn|See).+$/, ""),
`=HYPERLINK("${message.getThread().getPermalink()}";"View")`
];
}
}
return false;
};
const writeBouncedEmails_ = (data = []) => {
if (data.length > 0) {
toast_("Writing data to Google Sheet..");
const sheet = SpreadsheetApp.getActiveSheet();
sheet
.getRange(3, 1, sheet.getLastRow(), sheet.getLastColumn())
.clearContent();
sheet.getRange(3, 1, data.length, data[0].length).setValues(data);
SpreadsheetApp.flush();
toast_("Bounce Report is ready!");
showCredit_();
}
};
const findBouncedEmails_ = () => {
try {
const rows = [];
const { messages = [] } = Gmail.Users.Messages.list("me", {
q: "from:mailer-daemon",
maxResults: 200
});
if (messages.length === 0) {
toast_("No bounced emails found in your mailbox!");
return;
}
toast_("Working..");
for (let m = 0; m < messages.length; m += 1) {
const bounceData = parseMessage_(messages[m].id);
if (bounceData) {
rows.push(bounceData);
}
if (rows.length % 10 === 0) {
toast_(`${rows.length} bounced emails found so far..`);
}
}
writeBouncedEmails_(rows);
} catch (error) {
toast_(error.toString());
}
};
const onOpen = e => {
SpreadsheetApp.getUi()
.createMenu("🕵🏻‍♂️ Bounced Emails")
.addItem("Run Report", "findBouncedEmails_")
.addSeparator()
.addItem("Credits", "showCredit_")
.addToUi();
};
const showCredit_ = () => {
const template = HtmlService.createHtmlOutputFromFile("help");
const html = template
.setTitle("Bounce Report for Gmail")
.setWidth(460)
.setHeight(225);
SpreadsheetApp.getActiveSpreadsheet().show(html);
}

Related

Email multiple cells to recipient instead of one email per cell using Apps Script

Currently I'm working on a script that checks values of one column and based on that send email containing information from another column. Everything works perfectly except one thing - it send one email per value and I'd like to send all the values in one email. Can someone please help with the issue ?
const helpsheet = SpreadsheetApp.openById("ID").getSheetByName('Sheet');
const date = helpsheet.getRange(1,10).getValue();
const ss = SpreadsheetApp.openById("ID2");
const sh = ss.getSheetByName('Sheet2');
const data = sh.getRange('A2:c'+sh.getLastRow()).getValues();
var recipients = 'EMAIL#EMAIL';
var subject = 'SUBJECT';
data.forEach(r=>{
let overdueValue = r[2];
if (overdueValue > date)
{
let path = r[0];
MailApp.sendEmail({
to: recipients,
subject: subject,
htmlBody: 'Hi guys ' + path +'!<br><br>Best regards,'
});
}
});
} ```
Of course I can't test this because 1st I don't want a bunch of emails to myself and 2nd I don't have a data set that matches your data, but I'm pretty sure this will do what you want. What I do is build an array of rows that pass the sniff test using the Array push method below. Then when I have all the rows that pass I send one email.
I have edited this post to include functions to create an html table.
function test() {
try {
const helpsheet = SpreadsheetApp.openById("ID").getSheetByName('Sheet');
const date = helpsheet.getRange(1,10).getValue();
const ss = SpreadsheetApp.openById("ID2");
const sh = ss.getSheetByName('Sheet2');
const data = sh.getRange('A2:c'+sh.getLastRow()).getValues();
var pre = "Hello";
var post = "Best regards";
var recipients = 'EMAIL#EMAIL';
var subject = 'SUBJECT';
var passed = [];
data.forEach( r => {
let overdueValue = r[2];
if (overdueValue > date) {
passed.push(r);
}
});
if( passed.length > 0 ) { // maybe nothing passed the test
var html = createHTMLfile(true);
html = html.concat("<p>"+pre+"</p>");
html = html.concat(createTable(passed));
html = html.concat(createHTMLfile());
html = html.concat("<p>"+post+"</p>");
MailApp.sendEmail( {
to: email,
subject: subject,
htmlBody: html });
};
}
}
catch(err) {
console.log(err);
}
}
I have added the additional functions to create the table. You are welcome to play with the <style>s.
function createHTMLfile(pre) {
if( pre ) {
return "<html><head><style>table, td { border: thin solid black; border-collapse:collapse; text-align:center }</style></head><body>";
}
else {
return "</body></html>";
}
}
function createTable(data) {
try {
var width = 600/data[0].length;
var table = "<table>";
function addCell(value) {
table = table.concat("<td style=width:"+width+"px>");
table = table.concat(value.toString());
table = table.concat("</td>");
}
function addRow(row) {
table = table.concat("<tr>");
row.forEach( addCell );
table = table.concat("</tr>");
}
data.forEach( addRow )
table = table.concat("</table>");
return table;
}
catch(err) {
console.log(err);
}
}

How do I generate SEO URL's using Instantsearch

I'm using Algolia's Instantsearch.js with Typesense adapter, which works well for my search page, but if I go to the search page with a query or refinement in the URL, it doesn't filter. For example, if I do https://example.com/buy-list/buying/Sports (sports being the category), it doesn't automatically filter.
Here is the relevant snippet. I've narrowed it down to somewhere in the router because if I set it router: true, it works, it's just not pretty URL's.
const search = instantsearch({
searchClient,
indexName: INSTANT_SEARCH_INDEX_NAME,
routing: {
router: instantSearchRouter({
windowTitle({category, query}) {
const queryTitle = query ? `Results for "${query}"` : 'Buy List | DA Card World';
if (category) {
return `${category} – ${queryTitle}`;
}
return queryTitle;
},
createURL({qsModule, routeState, location}) {
const urlParts = location.href.match(/^(.*?)\/buying\/buy-list/);
const baseUrl = `${urlParts ? urlParts[1] : ''}/`;
const categoryPath = routeState.category
? `${getCategorySlug(routeState.category)}/`
: '';
const queryParameters = {};
if (routeState.query) {
queryParameters.query = encodeURIComponent(routeState.query);
}
if (routeState.page !== 1) {
queryParameters.page = routeState.page;
}
if (routeState.types) {
queryParameters.types = routeState.types.map(encodeURIComponent);
}
if (routeState.years) {
queryParameters.years = routeState.years.map(encodeURIComponent);
}
if (routeState.series) {
queryParameters.series = routeState.series.map(encodeURIComponent);
}
const queryString = qsModule.stringify(queryParameters, {
addQueryPrefix: true,
arrayFormat: 'repeat'
});
return `${baseUrl}buying/buy-list/${categoryPath}${queryString}`;
},
parseURL({qsModule, location}) {
const pathnameMatches = location.pathname.match(/\/buying\/buy-list\/(.*?)\/?$/);
const category = getCategoryName(
(pathnameMatches && pathnameMatches[1]) || ''
);
const {query = '', page, types = [], years = [], series = []} = qsModule.parse(
location.search.slice(1)
);
// `qs` does not return an array when there's a single value.
const allTypes = Array.isArray(types) ? types : [types].filter(Boolean);
const allYears = Array.isArray(years) ? years : [years].filter(Boolean);
const allSeries = Array.isArray(series) ? series : [series].filter(Boolean);
return {
query: decodeURIComponent(query),
page,
types: allTypes.map(decodeURIComponent),
years: allYears.map(decodeURIComponent),
series: allSeries.map(decodeURIComponent),
category
};
}
}),
stateMapping: {
stateToRoute(uiState) {
const indexUiState = uiState[INSTANT_SEARCH_INDEX_NAME] || {};
return {
query: indexUiState.query,
page: indexUiState.page,
types: indexUiState.refinementList && indexUiState.refinementList.package,
years: indexUiState.refinementList && indexUiState.refinementList.year,
series: indexUiState.refinementList && indexUiState.refinementList.series,
category: indexUiState.hierarchicalMenu && indexUiState.hierarchicalMenu[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]
&& indexUiState.hierarchicalMenu[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE].join('>')
};
},
routeToState(routeState) {
return {
instant_search: {
query: routeState.query,
page: routeState.page,
hierarchicalMenu: {
"category": routeState.category && routeState.category.split('>')
},
refinementList: {
packages: routeState.types,
years: routeState.years,
series: routeState.series
}
}
};
}
}
},
searchFunction: function (helper) {
helper.search();
const title = document.querySelector('title');
const header = document.querySelector('#results_title');
let titleText = 'Buy Lists';
if (INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE in helper.state.hierarchicalFacetsRefinements) {
titleText = helper.state.hierarchicalFacetsRefinements[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE] + ' Buy List';
}
title.text = titleText + ' | DA Card World';
header.innerText = titleText;
}
});

How to open only some UPI apps in IONIC 4 using UPI Deep Link

I am using webintent plugin for showing the installed UPI and doing the transaction with that UPI in IONIC 4. But I want to open only PhonePe, BHIM and Paytm and Google Pay applications. How can hide other UPI apps like CRED, AmazonPay, Whatsapp etc. Below is my code please suggest me.
payWithUPI() {
const packages = {
'paytm': 'net.one97.paytm',
'google': 'com.google.android.apps.nbu.paisa.user',
'bhim': 'in.org.npci.upiapp',
'phonepe': 'com.phonepe.app'
};
const tid = this.getRandomString();
const orderId = this.getRandomString();
const totalPrice = 1.00;
const UPI_ID = '9960777572#okbizaxis';
const UPI_NAME = 'Adhikar Patil';
const UPI_TXN_NOTE = 'TEST TXN';
let uri = `upi://pay?pa=${UPI_ID}&pn=${UPI_NAME}&tid=${tid}&am=${totalPrice}&cu=INR&tn=${UPI_TXN_NOTE}&tr=${orderId}`;
// uri = uri.replace(' ', '+');
(window as any).plugins.intentShim.startActivity(
{
action: this.webIntent.ACTION_VIEW,
url: uri,
requestCode: 1
}, intent => {
if (intent.extras.requestCode === 1 && intent.extras.resultCode === (window as any).plugins.intentShim.RESULT_OK && intent.extras.Status && (((intent.extras.Status as string).toLowerCase()) === ('success'))) {
alert("Payment Success");
}
else (intent.extras.requestCode === 1 && intent.extras.resultCode === (window as any).plugins.intentShim.RESULT_OK && intent.extras.Status && (((intent.extras.Status as string).toLowerCase()) === ('failed'))) {
alert("Payment Failed ") ;
}
}, err => {
alert('error ' + err);
});
}
getRandomString() {
const len = 10;
const arr = '1234567890asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM';
let ans = '';
for (let i = len; i > 0; i--) {
ans += arr[Math.floor(Math.random() * arr.length)];
}
return ans;
}

Chrome Developer Tools - Performance profiling

In the below image (from chrome performance profiling tab for a API call), what is resource loading which costs 719 ms ?
If I visit the network tab, for the same API call, I see only 10.05 seconds.
What is resource loading mean here ? Is there any specific activity the browser does after receiving the data ?
As #wOxxOM stated, buildNetworkRequestDetails is being called from Source Code
From the statement by #Sanju singh :
that statement doesn't tell anything, why it is taking that much time to make the resource available?
I think its necessary to break down exactly what is happening..
Summary:
Activity Browser and Network Activity are using different algorithms for calculating completion. Network Activity is calculating the response times from the request and Activity Browser is calculating the response time + time it took to add it into the WebInspector tracer.
Looking at
/**
* #param {!TimelineModel.TimelineModel.NetworkRequest} request
* #param {!TimelineModel.TimelineModel.TimelineModelImpl} model
* #param {!Components.Linkifier.Linkifier} linkifier
* #return {!Promise<!DocumentFragment>}
*/
static async buildNetworkRequestDetails(request, model, linkifier) {
const target = model.targetByEvent(request.children[0]);
const contentHelper = new TimelineDetailsContentHelper(target, linkifier);
const category = TimelineUIUtils.networkRequestCategory(request);
const color = TimelineUIUtils.networkCategoryColor(category);
contentHelper.addSection(ls`Network request`, color);
if (request.url) {
contentHelper.appendElementRow(ls`URL`, Components.Linkifier.Linkifier.linkifyURL(request.url));
}
// The time from queueing the request until resource processing is finished.
const fullDuration = request.endTime - (request.getStartTime() || -Infinity);
if (isFinite(fullDuration)) {
let textRow = Number.millisToString(fullDuration, true);
// The time from queueing the request until the download is finished. This
// corresponds to the total time reported for the request in the network tab.
const networkDuration = request.finishTime - request.getStartTime();
// The time it takes to make the resource available to the renderer process.
const processingDuration = request.endTime - request.finishTime;
if (isFinite(networkDuration) && isFinite(processingDuration)) {
const networkDurationStr = Number.millisToString(networkDuration, true);
const processingDurationStr = Number.millisToString(processingDuration, true);
const cacheOrNetworkLabel = request.cached() ? ls`load from cache` : ls`network transfer`;
textRow += ls` (${networkDurationStr} ${cacheOrNetworkLabel} + ${processingDurationStr} resource loading)`;
}
contentHelper.appendTextRow(ls`Duration`, textRow);
}
if (request.requestMethod) {
contentHelper.appendTextRow(ls`Request Method`, request.requestMethod);
}
if (typeof request.priority === 'string') {
const priority = PerfUI.NetworkPriorities.uiLabelForNetworkPriority(
/** #type {!Protocol.Network.ResourcePriority} */ (request.priority));
contentHelper.appendTextRow(ls`Priority`, priority);
}
if (request.mimeType) {
contentHelper.appendTextRow(ls`Mime Type`, request.mimeType);
}
let lengthText = '';
if (request.memoryCached()) {
lengthText += ls` (from memory cache)`;
} else if (request.cached()) {
lengthText += ls` (from cache)`;
} else if (request.timing && request.timing.pushStart) {
lengthText += ls` (from push)`;
}
if (request.fromServiceWorker) {
lengthText += ls` (from service worker)`;
}
if (request.encodedDataLength || !lengthText) {
lengthText = `${Number.bytesToString(request.encodedDataLength)}${lengthText}`;
}
contentHelper.appendTextRow(ls`Encoded Data`, lengthText);
if (request.decodedBodyLength) {
contentHelper.appendTextRow(ls`Decoded Body`, Number.bytesToString(request.decodedBodyLength));
}
const title = ls`Initiator`;
const sendRequest = request.children[0];
const topFrame = TimelineModel.TimelineModel.TimelineData.forEvent(sendRequest).topFrame();
if (topFrame) {
const link = linkifier.maybeLinkifyConsoleCallFrame(target, topFrame, {tabStop: true});
if (link) {
contentHelper.appendElementRow(title, link);
}
} else {
const initiator = TimelineModel.TimelineModel.TimelineData.forEvent(sendRequest).initiator();
if (initiator) {
const initiatorURL = TimelineModel.TimelineModel.TimelineData.forEvent(initiator).url;
if (initiatorURL) {
const link = linkifier.maybeLinkifyScriptLocation(target, null, initiatorURL, 0, {tabStop: true});
if (link) {
contentHelper.appendElementRow(title, link);
}
}
}
}
if (!request.previewElement && request.url && target) {
request.previewElement = await Components.ImagePreview.ImagePreview.build(
target, request.url, false,
{imageAltText: Components.ImagePreview.ImagePreview.defaultAltTextForImageURL(request.url)});
}
if (request.previewElement) {
contentHelper.appendElementRow(ls`Preview`, request.previewElement);
}
return contentHelper.fragment;
}
We can easily see that the request parameter is of type
`TimelineModel.TimelineModel.NetworkRequest`
NetWorkRequest has the following code:
_didStopRecordingTraceEvents: function()
{
var metadataEvents = this._processMetadataEvents();
this._injectCpuProfileEvents(metadataEvents);
this._tracingModel.tracingComplete();
this._resetProcessingState();
var startTime = 0;
for (var i = 0, length = metadataEvents.page.length; i < length; i++) {
var metaEvent = metadataEvents.page[i];
var process = metaEvent.thread.process();
var endTime = i + 1 < length ? metadataEvents.page[i + 1].startTime : Infinity;
this._currentPage = metaEvent.args["data"] && metaEvent.args["data"]["page"];
for (var thread of process.sortedThreads()) {
if (thread.name() === WebInspector.TimelineModel.WorkerThreadName && !metadataEvents.workers.some(function(e) { return e.args["data"]["workerThreadId"] === thread.id(); }))
continue;
this._processThreadEvents(startTime, endTime, metaEvent.thread, thread);
}
startTime = endTime;
}
this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
this._cpuProfiles = null;
this._buildTimelineRecords();
this._buildGPUTasks();
this._insertFirstPaintEvent();
this._resetProcessingState();
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
},
We can see that endTime is being calculated from:
metaEvent.thread.process()
We can see that metaEvent.page is being set by:
_processMetadataEvents: function()
{
var metadataEvents = this._tracingModel.devToolsMetadataEvents();
var pageDevToolsMetadataEvents = [];
var workersDevToolsMetadataEvents = [];
for (var event of metadataEvents) {
if (event.name === WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage)
pageDevToolsMetadataEvents.push(event);
else if (event.name === WebInspector.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker)
workersDevToolsMetadataEvents.push(event);
}
if (!pageDevToolsMetadataEvents.length) {
// The trace is probably coming not from DevTools. Make a mock Metadata event.
var pageMetaEvent = this._loadedFromFile ? this._makeMockPageMetadataEvent() : null;
if (!pageMetaEvent) {
console.error(WebInspector.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage + " event not found.");
return {page: [], workers: []};
}
pageDevToolsMetadataEvents.push(pageMetaEvent);
}
var sessionId = pageDevToolsMetadataEvents[0].args["sessionId"] || pageDevToolsMetadataEvents[0].args["data"]["sessionId"];
this._sessionId = sessionId;
var mismatchingIds = new Set();
/**
* #param {!WebInspector.TracingModel.Event} event
* #return {boolean}
*/
function checkSessionId(event)
{
var args = event.args;
// FIXME: put sessionId into args["data"] for TracingStartedInPage event.
if (args["data"])
args = args["data"];
var id = args["sessionId"];
if (id === sessionId)
return true;
mismatchingIds.add(id);
return false;
}
var result = {
page: pageDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime),
workers: workersDevToolsMetadataEvents.filter(checkSessionId).sort(WebInspector.TracingModel.Event.compareStartTime)
};
if (mismatchingIds.size)
WebInspector.console.error("Timeline recording was started in more than one page simultaneously. Session id mismatch: " + this._sessionId + " and " + mismatchingIds.valuesArray() + ".");
return result;
}

error { error: bind message supplies 11 parameters, but prepared statement "" requires 12

I have created post api but not able to figure out why am I getting this error ? Any suggestion for what I need to change in my query?
Query :
router.post('/bills', function(req, httpres, next) {
console.log("Inside the bills api");
const name = req.body.name;const designation = req.body.designation; const department = req.body.department;
const address = req.body.address;const phone = req.body.phone;const mobile = req.body.mobile;const email = req.body.email;
const organization = req.body.organization;const city = req.body.city;const state = req.body.state;const pincode = req.body.pincode;const fax = req.body.fax;
console.log(name,designation,department,address,phone,mobile,email,organization,city,state,pincode,fax)
pool.query("Insert into bill_to(name,designation,department,address,phone,mobile,email,organization,city,state,pincode,fax) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)",[name,designation.department,address,phone,mobile,email,organization,city,state,pincode,fax])
.subscribe(
data => {
console.log('success',data)
/*
if(data.rowCount > 0){
httpres.json({status : true ,message : ' ok',parameters:req.body });
}else{
httpres.send('error');
}*/
if(data.rows[0].exists){
httpres.json({status : true ,message : 'data inserted',parameters:req.body });
}else{
httpres.send('error');
}
}, err => {
console.log('error',err)
httpres.send('error');
})
You have put a period (.) instead of a comma (,) between designation and department in the pool.query:
... [name,designation.department,address,phone,mobile,email,organization,city,state,pincode,fax])
Change it to a comma and it should work:
... [name,designation,department,address,phone,mobile,email,organization,city,state,pincode,fax])