Files
Emailsorter/server/services/gmail.mjs
ANDJ abf761db07 Email Sorter Beta
Ich habe soweit automatisiert the Emails sortieren aber ich muss noch schauen was es fur bugs es gibt wenn die app online  ist deswegen wurde ich mit diesen Commit die website veroffentlichen obwohjl es sein konnte  das es noch nicht fertig ist und verkaufs bereit
2026-01-22 19:32:12 +01:00

358 lines
9.2 KiB
JavaScript

/**
* Gmail Service
* Handles Gmail API operations
*/
import { google } from 'googleapis'
import { OAuth2Client } from 'google-auth-library'
import { config } from '../config/index.mjs'
import { log } from '../middleware/logger.mjs'
/**
* Gmail Service Class
*/
export class GmailService {
constructor(accessToken, refreshToken = null) {
this.auth = new OAuth2Client(
config.google.clientId,
config.google.clientSecret,
config.google.redirectUri
)
this.auth.setCredentials({
access_token: accessToken,
refresh_token: refreshToken,
})
this.gmail = google.gmail({ version: 'v1', auth: this.auth })
}
/**
* Get user's email address
*/
async getProfile() {
const { data } = await this.gmail.users.getProfile({ userId: 'me' })
return data
}
/**
* List emails from inbox
* @param {number} maxResults - Maximum number of emails to fetch
* @param {string} pageToken - Pagination token
* @param {string} query - Gmail search query
*/
async listEmails(maxResults = 50, pageToken = null, query = 'in:inbox is:unread') {
const params = {
userId: 'me',
maxResults,
q: query,
}
if (pageToken) {
params.pageToken = pageToken
}
const { data } = await this.gmail.users.messages.list(params)
return {
messages: data.messages || [],
nextPageToken: data.nextPageToken,
resultSizeEstimate: data.resultSizeEstimate,
}
}
/**
* Get full email details
* @param {string} messageId - Message ID
*/
async getEmail(messageId) {
const { data } = await this.gmail.users.messages.get({
userId: 'me',
id: messageId,
format: 'full',
})
// Parse headers
const headers = {}
data.payload?.headers?.forEach(h => {
headers[h.name.toLowerCase()] = h.value
})
return {
id: data.id,
threadId: data.threadId,
snippet: data.snippet,
labelIds: data.labelIds || [],
headers,
internalDate: data.internalDate,
sizeEstimate: data.sizeEstimate,
}
}
/**
* Batch get multiple emails
* @param {string[]} messageIds - Array of message IDs
*/
async batchGetEmails(messageIds) {
// Gmail API supports batch requests, but we'll do simple parallel for now
const emails = await Promise.all(
messageIds.map(id => this.getEmail(id).catch(e => {
log.warn(`E-Mail abrufen fehlgeschlagen: ${id}`, { error: e.message })
return null
}))
)
return emails.filter(Boolean)
}
/**
* Create or get a label
* @param {string} name - Label name (e.g., "EmailSorter/VIP")
* @param {string} color - Optional label color (must be from Gmail's palette)
*/
async createLabel(name, color = null) {
// Gmail's allowed label colors (background colors)
const GMAIL_COLORS = {
red: { backgroundColor: '#fb4c2f', textColor: '#ffffff' },
orange: { backgroundColor: '#ffad47', textColor: '#000000' },
yellow: { backgroundColor: '#fad165', textColor: '#000000' },
green: { backgroundColor: '#16a766', textColor: '#ffffff' },
teal: { backgroundColor: '#43d692', textColor: '#000000' },
blue: { backgroundColor: '#4a86e8', textColor: '#ffffff' },
purple: { backgroundColor: '#a479e2', textColor: '#ffffff' },
pink: { backgroundColor: '#f691b3', textColor: '#000000' },
gray: { backgroundColor: '#666666', textColor: '#ffffff' },
}
// Map our colors to Gmail colors
const colorMap = {
'#ff0000': GMAIL_COLORS.red, // VIP
'#4285f4': GMAIL_COLORS.blue, // Kunden
'#0f9d58': GMAIL_COLORS.green, // Rechnungen
'#9c27b0': GMAIL_COLORS.purple, // Newsletter
'#ff9800': GMAIL_COLORS.orange, // Werbung
'#00bcd4': GMAIL_COLORS.teal, // Social
'#f44336': GMAIL_COLORS.red, // Security
'#673ab7': GMAIL_COLORS.purple, // Kalender
'#607d8b': GMAIL_COLORS.gray, // Review
}
try {
// Check if label exists
const { data } = await this.gmail.users.labels.list({ userId: 'me' })
const existing = data.labels?.find(l => l.name === name)
if (existing) {
return existing
}
// Create new label
const labelData = {
name,
labelListVisibility: 'labelShow',
messageListVisibility: 'show',
}
// Use mapped Gmail color if available
if (color && colorMap[color]) {
labelData.color = colorMap[color]
} else if (color) {
// Default to blue if color not in map
labelData.color = GMAIL_COLORS.blue
}
const { data: created } = await this.gmail.users.labels.create({
userId: 'me',
requestBody: labelData,
})
log.success(`Gmail Label erstellt: ${name}`)
return created
} catch (error) {
log.error(`Label erstellen fehlgeschlagen: ${name}`, { error: error.message })
return null
}
}
/**
* Add labels to a message
* @param {string} messageId - Message ID
* @param {string[]} labelIds - Label IDs to add
*/
async addLabels(messageId, labelIds) {
await this.gmail.users.messages.modify({
userId: 'me',
id: messageId,
requestBody: {
addLabelIds: labelIds,
},
})
}
/**
* Remove labels from a message
* @param {string} messageId - Message ID
* @param {string[]} labelIds - Label IDs to remove
*/
async removeLabels(messageId, labelIds) {
await this.gmail.users.messages.modify({
userId: 'me',
id: messageId,
requestBody: {
removeLabelIds: labelIds,
},
})
}
/**
* Archive a message (remove from INBOX)
* @param {string} messageId - Message ID
*/
async archiveEmail(messageId) {
await this.removeLabels(messageId, ['INBOX'])
}
/**
* Move message to trash
* @param {string} messageId - Message ID
*/
async trashEmail(messageId) {
await this.gmail.users.messages.trash({
userId: 'me',
id: messageId,
})
}
/**
* Mark message as read
* @param {string} messageId - Message ID
*/
async markAsRead(messageId) {
await this.removeLabels(messageId, ['UNREAD'])
}
/**
* Mark message as unread
* @param {string} messageId - Message ID
*/
async markAsUnread(messageId) {
await this.addLabels(messageId, ['UNREAD'])
}
/**
* Star a message
* @param {string} messageId - Message ID
*/
async starEmail(messageId) {
await this.addLabels(messageId, ['STARRED'])
}
/**
* Get all labels
*/
async getLabels() {
const { data } = await this.gmail.users.labels.list({ userId: 'me' })
return data.labels || []
}
/**
* Delete a label by ID
*/
async deleteLabel(labelId) {
try {
await this.gmail.users.labels.delete({
userId: 'me',
id: labelId,
})
return true
} catch (error) {
log.warn(`Label löschen fehlgeschlagen: ${labelId}`, { error: error.message })
return false
}
}
/**
* Cleanup old EmailSorter labels
* Removes all labels starting with "EmailSorter/" and old German labels
*/
async cleanupOldLabels() {
// Old labels to remove (German and old format)
const OLD_LABELS = [
// Old "EmailSorter/" prefix labels
'EmailSorter/',
// Old German labels
'Wichtig', 'Kunden', 'Rechnungen', 'Sicherheit', 'Termine', 'Prüfen', 'Werbung',
'VIP / Wichtig', 'Kunden / Projekte', 'Rechnungen / Belege',
'Werbung / Promotions', 'Social / Benachrichtigungen', 'Security / 2FA',
'Kalender / Events', 'Review / Unklar',
]
try {
const labels = await this.getLabels()
// Filter labels that match old patterns
const labelsToDelete = labels.filter(l => {
if (!l.name) return false
// Check for EmailSorter/ prefix
if (l.name.startsWith('EmailSorter/')) return true
// Check for exact matches with old labels
if (OLD_LABELS.includes(l.name)) return true
return false
})
let deleted = 0
for (const label of labelsToDelete) {
if (await this.deleteLabel(label.id)) {
log.info(`Old label deleted: ${label.name}`)
deleted++
}
}
return deleted
} catch (error) {
log.error('Cleanup failed', { error: error.message })
return 0
}
}
/**
* Setup Gmail push notifications
* @param {string} webhookUrl - Webhook URL for notifications
*/
async setupWatch(webhookUrl) {
const { data } = await this.gmail.users.watch({
userId: 'me',
requestBody: {
labelIds: ['INBOX'],
topicName: webhookUrl, // Should be a Cloud Pub/Sub topic
labelFilterAction: 'include',
},
})
return data
}
/**
* Stop watching for push notifications
*/
async stopWatch() {
await this.gmail.users.stop({ userId: 'me' })
}
/**
* Get history changes since a historyId
* @param {string} startHistoryId - History ID to start from
*/
async getHistory(startHistoryId) {
const { data } = await this.gmail.users.history.list({
userId: 'me',
startHistoryId,
historyTypes: ['messageAdded', 'labelAdded', 'labelRemoved'],
})
return data.history || []
}
}
export default GmailService