-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NewAttachment, NewEmail: improvements
- Loading branch information
1 parent
6f5c3b3
commit e52caea
Showing
6 changed files
with
212 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ | |
"type": "text", | ||
"label": "Search Query", | ||
"index": 1, | ||
"tooltip": "The search query to find emails." | ||
"tooltip": "The search query to find emails. See <a target=_blank href=\"https://support.google.com/mail/answer/7190\">Google Documentation</a> for more info. Example: <code>from:[email protected] AND subject:dinner</code>." | ||
}, | ||
"outputType": { | ||
"type": "select", | ||
|
136 changes: 39 additions & 97 deletions
136
src/appmixer/google/gmail/NewAttachment/NewAttachment.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,116 +1,58 @@ | ||
'use strict'; | ||
|
||
const emailCommons = require('../gmail-commons'); | ||
const Promise = require('bluebird'); | ||
|
||
module.exports = { | ||
async tick(context) { | ||
let newState = {}; | ||
|
||
const { labels: { AND: labels } = { AND: [] } } = context.properties; | ||
const isLabelsEmpty = !labels.some(label => label.name); | ||
|
||
// Fetch new messages from the inbox | ||
const data = await emailCommons.listNewMessages( | ||
{ context, userId: 'me' }, | ||
context.state.id || null | ||
); | ||
|
||
// Update the state with the latest message ID | ||
newState.id = data.lastMessageId; | ||
|
||
// Fetch the full email data for new messages | ||
const emails = await Promise.map(data.newMessages, async message => { | ||
return emailCommons.callEndpoint(context, `/users/me/messages/${message.id}`, { | ||
method: 'GET', | ||
params: { format: 'full' } | ||
}).then(response => response.data).catch(err => { | ||
// email can be deleted (permanently) in gmail between listNewMessages call and | ||
// this getMessage call, in such case - ignore it and return null | ||
if (err && err.response && err.response.status === 404) { | ||
return null; | ||
} | ||
throw err; | ||
}); | ||
}, { concurrency: 10 }); | ||
async tick(context) { | ||
|
||
// Extract attachments from emails | ||
let attachments = await Promise.map(emails, email => { | ||
if (!email || !email.labelIds) { | ||
// Skip if the email was deleted or labelIds is missing | ||
return []; | ||
const { download } = context.properties; | ||
const state = context.state; | ||
let query = context.properties.query; | ||
query = (query ? query + ' AND ' : '') + 'has:attachment'; | ||
const { emails, state: newState } = await emailCommons.listNewMessages(context, query, state); | ||
|
||
// Fetch attachments from emails. | ||
const output = []; | ||
await Promise.map(emails, async (email) => { | ||
if (!email) { | ||
// Skip if the email was deleted. | ||
return; | ||
} | ||
|
||
// Filter emails based on selected labels | ||
if (isLabelsEmpty || labels.some(label => email.labelIds.includes(label.name))) { | ||
return downloadAttachments(context, email); | ||
if (!emailCommons.isNewInboxEmail(email.labelIds || [])) { | ||
// Skip SENT and DRAFT emails. | ||
return; | ||
} | ||
return []; | ||
}); | ||
|
||
// Flatten the array of attachments | ||
attachments = attachments.reduce((a, b) => a.concat(b), []); | ||
|
||
// Save attachments and send them to the output port | ||
let attachmentsOutput; | ||
|
||
if (context.properties.download) { | ||
attachmentsOutput = await Promise.map(attachments, attachment => { | ||
const buffer = Buffer.from(attachment.data, 'base64'); | ||
return context.saveFileStream( | ||
attachment.filename, | ||
buffer | ||
).then(res => { | ||
return Object.assign(res, { | ||
email: attachment.email, | ||
attachment | ||
}); | ||
}); | ||
}); | ||
} else { | ||
attachmentsOutput = attachments.map(attachment => { | ||
return Object.assign(attachment, { | ||
email: attachment.email, | ||
return Promise.map(email.attachments || [], async (attachment) => { | ||
const out = { | ||
email, | ||
attachment | ||
}); | ||
}; | ||
if (download) { | ||
const savedFile = await downloadAttachment(context, email.id, attachment.id, attachment.filename); | ||
out.fileId = savedFile.fileId; | ||
out.filename = savedFile.filename; | ||
out.contentType = savedFile.contentType; | ||
} | ||
output.push(out); | ||
}); | ||
} | ||
|
||
await Promise.map(attachmentsOutput, out => { | ||
return context.sendJson(out, 'attachment'); | ||
}); | ||
|
||
await context.saveState(newState); | ||
await context.sendArray(output, 'attachment'); | ||
if (JSON.stringify(state != JSON.stringify(newState))) { | ||
return context.saveState(newState); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Download attachments from an email. | ||
* @param {Context} context | ||
* @param {Object} email | ||
* @return {Array<Object>} returns array with attachments | ||
*/ | ||
const downloadAttachments = async (context, email) => { | ||
if (!emailCommons.isNewInboxEmail(email.labelIds)) { | ||
return []; // skip SENT and DRAFT emails | ||
} | ||
|
||
// Parse the email content to extract attachments | ||
const parsedEmail = emailCommons.normalizeEmail(email); | ||
|
||
return Promise.map(parsedEmail.attachments || [], async (attachment) => { | ||
const out = { | ||
filename: attachment.filename, | ||
mimetype: attachment.mimeType || 'application/octet-stream', // Ensure mimetype is set | ||
size: attachment.size, | ||
email: parsedEmail, | ||
attachment: attachment | ||
}; | ||
if (context.properties.download) { | ||
const response = await emailCommons.callEndpoint(context, `/users/me/messages/${email.id}/attachments/${attachment.id}`, { | ||
method: 'GET' | ||
}); | ||
out.data = response.data.data; | ||
} | ||
return out; | ||
const downloadAttachment = async (context, emailId, attachmentId, filename) => { | ||
const response = await emailCommons.callEndpoint(context, `/users/me/messages/${emailId}/attachments/${attachmentId}`, { | ||
method: 'GET' | ||
}); | ||
const base64 = response.data.data; | ||
const buffer = Buffer.from(base64, 'base64'); | ||
const savedFile = await context.saveFileStream(filename, buffer); | ||
return savedFile; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,37 +21,17 @@ | |
"properties": { | ||
"schema": { | ||
"properties": { | ||
"labels": { "type": "object" }, | ||
"query": { "type": "string" }, | ||
"download": { "type": "boolean" } | ||
} | ||
}, | ||
"inspector": { | ||
"inputs": { | ||
"labels": { | ||
"type": "expression", | ||
"label": "Labels", | ||
"levels": [ | ||
"AND" | ||
], | ||
"query": { | ||
"type": "text", | ||
"label": "Email Query", | ||
"index": 1, | ||
"tooltip": "Select one or more labels to filter the emails that contain attachments. The trigger will activate if an attachment is received in an email that matches any of the selected labels. If no labels are selected, the trigger will activate for all attachments in all received emails.", | ||
"fields": { | ||
"name": { | ||
"type": "select", | ||
"label": "Name", | ||
"index": 1, | ||
"tooltip": "Select a name of the existing label.", | ||
"source": { | ||
"url": "/component/appmixer/google/gmail/ListLabels?outPort=out", | ||
"data": { | ||
"properties": { | ||
"sendWholeArray": true | ||
}, | ||
"transform": "./ListLabels#labelsToSelectArrayFiltered" | ||
} | ||
} | ||
} | ||
} | ||
"tooltip": "The search query to find new emails. This allows you to only consider email messages that can be found using the query. See <a target=_blank href=\"https://support.google.com/mail/answer/7190\">Google Documentation</a> for more info. Example: <code>from:[email protected] AND subject:dinner</code>." | ||
}, | ||
"download": { | ||
"type": "toggle", | ||
|
@@ -67,36 +47,38 @@ | |
{ | ||
"name": "attachment", | ||
"options": [ | ||
{ "label": "File ID", "value": "fileId" }, | ||
{ "label": "File Name", "value": "filename" }, | ||
{ "label": "Content Type", "value": "contentType" }, | ||
{ "label": "File ID", "value": "fileId", "schema": { "type": "string", "format": "appmixer-file-id" } }, | ||
{ "label": "File Name", "value": "filename", "schema": { "type": "string" } }, | ||
{ "label": "Content Type", "value": "contentType", "schema": { "type": "string" } }, | ||
{ "label": "Attachment", "value": "attachment", "schema": { | ||
"type": "object", | ||
"properties": { | ||
"id": { "type": "string" }, | ||
"filename": { "type": "string" }, | ||
"size": { "type": "integer" }, | ||
"mimeType": { "type": "string" } | ||
"id": { "type": "string", "title": "Attachment ID" }, | ||
"filename": { "type": "string", "title": "Attachment File Name" }, | ||
"size": { "type": "integer", "title":"Attachment File Size" }, | ||
"mimeType": { "type": "string", "title": "Attachment MIME Type" } | ||
} | ||
} }, | ||
{ "label": "Email", "value": "email", | ||
"schema": { | ||
"type": "object", | ||
"properties": { | ||
"id": { "type": "string" }, | ||
"threadId": { "type": "string" }, | ||
"labelIds": { "type": "array", "items": { "type": "string" } }, | ||
"snippet": { "type": "string" }, | ||
"sizeEstimate": { "type": "integer" }, | ||
"id": { "type": "string", "title": "Email ID" }, | ||
"threadId": { "type": "string", "title": "Email Thread ID" }, | ||
"labelIds": { "type": "array", "items": { "type": "string" }, "title": "Email Label IDs" }, | ||
"snippet": { "type": "string", "title": "Email Snippet" }, | ||
"sizeEstimate": { "type": "integer", "title": "Email Size Estimate" }, | ||
"payload": { | ||
"type": "object", | ||
"title": "Email Payload", | ||
"properties": { | ||
"date": { "type": "string", "format": "date-time" }, | ||
"subject": { "type": "string" }, | ||
"text": { "type": "string" }, | ||
"html": { "type": "string" }, | ||
"date": { "type": "string", "format": "date-time", "title": "Email Date" }, | ||
"subject": { "type": "string", "title": "Email Subject" }, | ||
"text": { "type": "string", "title": "Email Text" }, | ||
"html": { "type": "string", "title": "Email HTML" }, | ||
"from": { | ||
"type": "array", | ||
"title": "Email Senders", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
|
@@ -107,6 +89,7 @@ | |
}, | ||
"to": { | ||
"type": "array", | ||
"title": "Email Recipients", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
|
@@ -119,6 +102,7 @@ | |
}, | ||
"attachments": { | ||
"type": "array", | ||
"title": "Email Attachments", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.