Mailspring/packages/nylas-message-processor/processors/threading.js

204 lines
5.4 KiB
JavaScript
Raw Normal View History

2016-06-24 04:15:30 +08:00
const {DatabaseConnector} = require('nylas-core')
2016-06-23 02:49:53 +08:00
2016-06-23 08:34:21 +08:00
function fetchCorrespondingThread({db, accountId, message}) {
const cleanedSubject = cleanSubject(message.subject)
return getThreads({db, message, cleanedSubject})
.then((threads) => {
return findCorrespondingThread({message, threads})
})
}
function getThreads({db, message, cleanedSubject}) {
const {Thread} = db
return Thread.findAll({
where: {
threadId: message.headers.threadId,
cleanedSubject: cleanedSubject,
},
order: [
['id', 'DESC']
],
})
}
function findCorrespondingThread({message, threads}) {
for (const thread of threads) {
for (const match of thread.messages) {
// Ignore BCC
const {matchEmails, messageEmails} = removeBccParticipants({message, match})
// A thread is probably related if it has more than two common participants
const intersectingParticipants = getIntersectingParticipants({messageEmails, matchEmails})
if (intersectingParticipants.length >= 2) {
if (thread.messages.length >= MAX_THREAD_LENGTH)
break
return match.thread
}
// Handle case for self-sent emails
if (!message.from || !message.to)
return
if (isSentToSelf({message, match})) {
if (thread.messages.length >= MAX_THREAD_LENGTH)
break
return match.thread
}
}
}
}
function removeBccParticipants({message, match}) {
const matchBcc = match.bcc ? match.bcc : []
const messageBcc = message.bcc ? message.bcc : []
let matchEmails = match.participants.filter((participant) => {
return matchBcc.find(bcc => bcc === participant)
})
matchEmails.map((email) => {
return email[1]
})
let messageEmails = message.participants.filter((participant) => {
return messageBcc.find(bcc => bcc === participant)
})
messageEmails.map((email) => {
return email[1]
})
return {messageEmails, matchEmails}
}
function getIntersectingParticipants({messageEmails, matchEmails}) {
const matchParticipants = new Set(matchEmails)
const messageParticipants = new Set(messageEmails)
const intersectingParticipants = new Set([...matchParticipants]
.filter(participant => messageParticipants.has(participant)))
return intersectingParticipants
}
function isSentToSelf({message, match}) {
const matchFrom = match.from.map((participant) => {
return participant[1]
})
const matchTo = match.to.map((participant) => {
return participant[1]
})
const messageFrom = message.from.map((participant) => {
return participant[1]
})
const messageTo = message.to.map((participant) => {
return participant[1]
})
return (messageTo.length === 1 &&
messageFrom === messageTo &&
matchFrom === matchTo &&
messageTo === matchFrom)
}
2016-06-24 06:44:03 +08:00
function cleanSubject(subject) {
if (subject === null) {
return ""
}
const regex = new RegExp(/^((re|fw|fwd|aw|wg|undeliverable|undelivered):\s*)+/ig)
const cleanedSubject = subject.replace(regex, () => "")
return cleanedSubject
}
function getThreadFromReferences({db, references}) {
const {Message} = db
const messageId = references.split()[references.length - 1]
return Message.find({where: {messageId: messageId}})
.then((message) => {
return message.getThread()
})
}
function matchThread({db, accountId, message}) {
const {Thread} = db
if (message.headers.references) {
return getThreadFromReferences()
.then((thread) => {
if (thread) {
return thread
}
return fetchCorrespondingThread({db, accountId, message})
.then((thread) => {
if (thread) {
return thread
}
return Thread.create({
subject: message.subject,
cleanedSubject: cleanSubject(message.subject),
2016-06-28 03:30:28 +08:00
firstMessageTimestamp: message.date,
2016-06-25 07:14:04 +08:00
unreadCount: 0,
2016-06-28 01:15:05 +08:00
starredCount: 0,
2016-06-24 06:44:03 +08:00
})
})
})
}
return fetchCorrespondingThread({db, accountId, message})
.then((thread) => {
if (thread) {
return thread
}
return Thread.create({
subject: message.subject,
cleanedSubject: cleanSubject(message.subject),
2016-06-28 03:30:28 +08:00
firstMessageTimestamp: message.date,
2016-06-25 07:14:04 +08:00
unreadCount: 0,
2016-06-28 01:15:05 +08:00
starredCount: 0,
2016-06-24 06:44:03 +08:00
})
})
}
function addMessageToThread({db, accountId, message}) {
const {Thread} = db
// Check for Gmail's own thread ID
if (message.headers['X-GM-THRID']) {
return Thread.find({where: {threadId: message.headers['X-GM-THRID']}})
}
return matchThread({db, accountId, message})
2016-06-28 03:30:28 +08:00
.then((thread) => ({db, thread}))
2016-06-24 06:44:03 +08:00
}
2016-06-28 03:30:28 +08:00
function updateThreadProperties({db, thread, message}) {
const {Category} = db;
Category.findById(message.CategoryId).then((category) => {
if (category.role !== 'sent') {
thread.lastMessageReceivedTimestamp = message.date;
thread.save();
}
})
thread.lastMessageTimestamp = message.date;
thread.hasCategory(message.CategoryId).then((hasCategory) => {
if (!hasCategory) {
thread.addCategory(message.CategoryId)
}
});
2016-06-25 07:14:04 +08:00
if (message.unread) {
thread.unreadCount++;
}
2016-06-28 01:15:05 +08:00
if (message.starred) {
thread.starredCount++;
}
thread.save();
2016-06-25 07:14:04 +08:00
}
2016-06-24 06:44:03 +08:00
function processMessage({message, accountId}) {
return DatabaseConnector.forAccount(accountId)
.then((db) => addMessageToThread({db, accountId, message}))
2016-06-28 03:30:28 +08:00
.then(({db, thread}) => {
2016-06-24 06:44:03 +08:00
thread.addMessage(message)
message.setThread(thread)
2016-06-28 03:30:28 +08:00
updateThreadProperties({db, thread, message})
2016-06-24 06:44:03 +08:00
return message
})
}
2016-06-23 08:34:21 +08:00
module.exports = {
2016-06-24 01:26:41 +08:00
order: 1,
2016-06-23 02:49:53 +08:00
processMessage,
}