Skip to content

Instantly share code, notes, and snippets.

@tranphuoctien
Created July 30, 2020 04:41
Show Gist options
  • Save tranphuoctien/facbeb022749139ac882fff6ec4695fb to your computer and use it in GitHub Desktop.
Save tranphuoctien/facbeb022749139ac882fff6ec4695fb to your computer and use it in GitHub Desktop.
convert gmail api response to imap structure data.
const { Base64 } = require('js-base64');
const _ = require('underscore');
const { parseAddresses } = require('mimelib');
const { isArray } = require('underscore');
/**
* Decodes a url safe Base64 string to its original representation.
* @param {string} string
* @return {string}
*/
function urlB64Decode(string = '', isText = false) {
return `${Base64.decode(string)}`;
}
/**
* convert - to upcase and get need
*/
const imapHeaders = ['subject', 'references', 'date', 'to', 'from', 'to', 'cc', 'bcc', 'message-id', 'in-reply-to', 'reply-to'];
/**
* Takes the header array filled with objects and transforms it into a more
* pleasant key-value object.
* @param {array} headers
* @return {object}
*/
function indexHeaders(headers) {
if (!headers) {
return {};
} else {
return headers.reduce(function (result, header) {
switch (header.name.toLowerCase()) {
case 'priority':
case 'x-priority':
case 'x-msmail-priority':
case 'importance':
header.name = 'priority';
header.value = parsePriority(header.value);
break;
case 'from':
case 'to':
case 'cc':
case 'bcc':
case 'sender':
case 'reply-to':
case 'delivered-to':
case 'return-path':
header.value = parseAddresses(header.value);
break;
case 'references':
if (header.value && !isArray(header.value)) {
let _refs = header.value.split(" ");
_refs = _refs.map(ref => ref.slice(1, -1));
header.value = _refs;
}
break;
case 'message-id':
if (header.value) {
header.value = header.value.slice(1, -1);
}
break;
}
let headerName = header.name.toLowerCase();
if (!result[headerName]) {
result[headerName] = header.value;
}
return result;
}, {});
}
}
/**
* Parse priority mail [low,normal,high];
* @param {*} value
*/
function parsePriority(value) {
value = value.toLowerCase().trim();
if (!isNaN(parseInt(value, 10))) {
// support "X-Priority: 1 (Highest)"
value = parseInt(value, 10) || 0;
if (value === 3) {
return 'normal';
} else if (value > 3) {
return 'low';
} else {
return 'high';
}
} else {
switch (value) {
case 'non-urgent':
case 'low':
return 'low';
case 'urgent':
case 'high':
return 'high';
}
}
return 'normal';
}
/**
* Takes a response from the Gmail API's GET message method and extracts all
* the relevant data.
* @param {object} response
* @return {object}
*/
module.exports = function parseMessage(response) {
// init result
let result = {
id: response.id,
messageId: null,
messageUID: response.id,
threadId: response.threadId,
labelIds: response.labelIds.map(lb => lb.toUpperCase()),
snippet: response.snippet,
historyId: response.historyId,
uid: response.id,
path: response.path || null,
attributes: [],
headers: null,
account: null,
accountId: response.accountId || null,
accountType: 1,
itemType: "EMAIL",
html: "",
htmlExists: false, // be consistent with Flo mail object
text: "",
notAlreadyAttachments: [],
previewId: "",
flags: []
};
if (response.internalDate) {
result.internalDate = parseInt(response.internalDate);
}
if(!result.imageAttachments) {
result.imageAttachments = [];
}
// payload is content is mail
const payload = response.payload;
if (!payload) {
return result;
}
// return filter header of email
let headers = indexHeaders(payload.headers);
let extraInfo = [];
// what you want to convert upercase
imapHeaders.forEach(key => {
let isFind = Object.keys(headers).find(hkey => hkey === key);
if (isFind) {
extraInfo[key.replace(/-([a-z])/g, (m, c) => c.toUpperCase())] = headers[key];
}
});
// map header
result = _.extend(result, extraInfo);
result.previewId = extraInfo.messageId;
// full header
result.headers = headers;
result.hasAttachment = headers['content-type'].includes('multipart/mixed');
let parts = [payload];
let firstPartProcessed = false;
while (parts.length !== 0) {
let part = parts.shift();
if (part.parts) {
parts = parts.concat(part.parts);
}
if (firstPartProcessed) {
headers = indexHeaders(part.headers);
}
if (!part.body) {
continue;
}
let partId = parseFloat(part.partId) || 0;
let mintypeParts = part.mimeType.split('/');
let isHtml = part.mimeType && part.mimeType.indexOf('text/html') !== -1;
let isPlain = part.mimeType && part.mimeType.indexOf('text/plain') !== -1;
let isAttachment = Boolean(part.body.attachmentId || (headers['content-disposition'] && headers['content-disposition'].toLowerCase().indexOf('attachment') !== -1));
let isInline = headers['content-disposition'] && headers['content-disposition'].toLowerCase().indexOf('inline') !== -1;
// check part in body or not
let isInBody = Boolean(partId < 1);
// what's in header
let gheaders = indexHeaders(part.headers);
if (isHtml && !isAttachment) {
result.html = (urlB64Decode(part.body.data, isPlain)).replace(/(<style[\w\W]+style>)/g, '');
result.htmlExists = true;
} else if (isPlain && !isAttachment) {
result.text = urlB64Decode(part.body.data, isPlain);
} else if (isAttachment || isInline) {
let body = part.body;
let attachment = {
id: body.attachmentId,
contentId: gheaders['x-attachment-id'],
description: '',
disposition: {
type: "ATTACHMENT",
params: {
filename: part.filename
}
},
fileName: part.filename,
generatedFileName: part.filename,
subtype: mintypeParts[1],
type: mintypeParts[0],
encoding: gheaders['content-transfer-encoding'],
params: {
name: ""
},
partID: partId,
length: body.size,
size: body.size,
language: null,
md5: null
}
const isImgAttachment = attachment.disposition && /^attachment/i.test(attachment.disposition.type) && attachment.type === 'image';
if (isInBody === false && isInline === false) {
if (!result.attachments) {
result.attachments = [];
}
result.attachments.push(attachment);
if (isImgAttachment) {
result.imageAttachments.push({
id: body.attachmentId,
cid: gheaders['content-id'],
contentDisposition: 'inline',
contentType: part.mimeType,
fileName: part.filename,
partId: partId,
url: ''
});
}
} else {
if (!result.inlineAttachments) {
result.inlineAttachments = [];
}
let inlineAttachment = {
id: body.attachmentId,
cid: gheaders['content-id'],
contentDisposition: 'inline',
contentType: part.mimeType,
fileName: part.filename,
partId: partId,
url: ''
};
result.inlineAttachments.push(inlineAttachment);
}
}
firstPartProcessed = true;
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment