Files
Emailsorter/server/services/outlook.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

336 lines
8.9 KiB
JavaScript

/**
* Outlook Service
* Handles Microsoft Graph API operations for Outlook mail
*/
import { log } from '../middleware/logger.mjs'
const GRAPH_API_BASE = 'https://graph.microsoft.com/v1.0'
/**
* Outlook Service Class
*/
export class OutlookService {
constructor(accessToken) {
this.accessToken = accessToken
this.headers = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
}
/**
* Make Graph API request
* @private
*/
async _request(endpoint, options = {}) {
const url = endpoint.startsWith('http') ? endpoint : `${GRAPH_API_BASE}${endpoint}`
const response = await fetch(url, {
...options,
headers: {
...this.headers,
...options.headers,
},
})
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new Error(error.error?.message || `Graph API Error: ${response.status}`)
}
// Handle 204 No Content
if (response.status === 204) {
return null
}
return response.json()
}
/**
* Get user profile
*/
async getProfile() {
return this._request('/me')
}
/**
* List emails from inbox
* @param {number} top - Number of emails to fetch
* @param {string} skip - Skip token for pagination
* @param {string} filter - OData filter
*/
async listEmails(top = 50, skip = null, filter = null) {
let endpoint = `/me/mailFolders/inbox/messages?$top=${top}&$select=id,subject,from,bodyPreview,receivedDateTime,isRead,categories`
if (filter) {
endpoint += `&$filter=${encodeURIComponent(filter)}`
}
if (skip) {
endpoint += `&$skip=${skip}`
}
const data = await this._request(endpoint)
return {
messages: data.value || [],
nextLink: data['@odata.nextLink'],
count: data['@odata.count'],
}
}
/**
* Get full email details
* @param {string} messageId - Message ID
*/
async getEmail(messageId) {
return this._request(`/me/messages/${messageId}?$select=id,subject,from,body,bodyPreview,receivedDateTime,isRead,categories,importance,flag`)
}
/**
* Batch get multiple emails
* @param {string[]} messageIds - Array of message IDs
*/
async batchGetEmails(messageIds) {
// Use Graph batch API for efficiency
const batchRequest = {
requests: messageIds.map((id, index) => ({
id: String(index),
method: 'GET',
url: `/me/messages/${id}?$select=id,subject,from,bodyPreview,receivedDateTime,categories`,
})),
}
const response = await this._request('/$batch', {
method: 'POST',
body: JSON.stringify(batchRequest),
})
return response.responses
.filter(r => r.status === 200)
.map(r => r.body)
}
/**
* Update message properties
* @param {string} messageId - Message ID
* @param {object} updates - Properties to update
*/
async updateMessage(messageId, updates) {
return this._request(`/me/messages/${messageId}`, {
method: 'PATCH',
body: JSON.stringify(updates),
})
}
/**
* Add categories to a message
* @param {string} messageId - Message ID
* @param {string[]} categories - Categories to add
*/
async addCategories(messageId, categories) {
const email = await this.getEmail(messageId)
const existingCategories = email.categories || []
const newCategories = [...new Set([...existingCategories, ...categories])]
return this.updateMessage(messageId, { categories: newCategories })
}
/**
* Remove categories from a message
* @param {string} messageId - Message ID
* @param {string[]} categories - Categories to remove
*/
async removeCategories(messageId, categories) {
const email = await this.getEmail(messageId)
const existingCategories = email.categories || []
const newCategories = existingCategories.filter(c => !categories.includes(c))
return this.updateMessage(messageId, { categories: newCategories })
}
/**
* Archive a message (move to archive folder)
* @param {string} messageId - Message ID
*/
async archiveEmail(messageId) {
// First, try to get or create archive folder
let archiveFolder
try {
archiveFolder = await this._request('/me/mailFolders/archive')
} catch {
// Archive folder might not exist, try to find it
const folders = await this._request('/me/mailFolders?$filter=displayName eq \'Archive\'')
if (folders.value?.length) {
archiveFolder = folders.value[0]
}
}
if (archiveFolder) {
return this._request(`/me/messages/${messageId}/move`, {
method: 'POST',
body: JSON.stringify({ destinationId: archiveFolder.id }),
})
}
// Fallback: just mark as read
return this.markAsRead(messageId)
}
/**
* Move message to deleted items
* @param {string} messageId - Message ID
*/
async deleteEmail(messageId) {
return this._request(`/me/messages/${messageId}/move`, {
method: 'POST',
body: JSON.stringify({ destinationId: 'deleteditems' }),
})
}
/**
* Move message to a folder
* @param {string} messageId - Message ID
* @param {string} folderId - Destination folder ID
*/
async moveEmail(messageId, folderId) {
return this._request(`/me/messages/${messageId}/move`, {
method: 'POST',
body: JSON.stringify({ destinationId: folderId }),
})
}
/**
* Mark message as read
* @param {string} messageId - Message ID
*/
async markAsRead(messageId) {
return this.updateMessage(messageId, { isRead: true })
}
/**
* Mark message as unread
* @param {string} messageId - Message ID
*/
async markAsUnread(messageId) {
return this.updateMessage(messageId, { isRead: false })
}
/**
* Flag a message
* @param {string} messageId - Message ID
* @param {string} flagStatus - 'flagged' | 'complete' | 'notFlagged'
*/
async flagEmail(messageId, flagStatus = 'flagged') {
return this.updateMessage(messageId, {
flag: { flagStatus },
})
}
/**
* Set message importance
* @param {string} messageId - Message ID
* @param {string} importance - 'low' | 'normal' | 'high'
*/
async setImportance(messageId, importance) {
return this.updateMessage(messageId, { importance })
}
/**
* Get all mail folders
*/
async getFolders() {
const data = await this._request('/me/mailFolders')
return data.value || []
}
/**
* Create a mail folder
* @param {string} displayName - Folder name
* @param {string} parentFolderId - Parent folder ID (optional)
*/
async createFolder(displayName, parentFolderId = null) {
const endpoint = parentFolderId
? `/me/mailFolders/${parentFolderId}/childFolders`
: '/me/mailFolders'
return this._request(endpoint, {
method: 'POST',
body: JSON.stringify({ displayName }),
})
}
/**
* Get available categories
*/
async getCategories() {
const data = await this._request('/me/outlook/masterCategories')
return data.value || []
}
/**
* Create a category
* @param {string} displayName - Category name
* @param {string} color - Color preset (e.g., 'preset0' to 'preset24')
*/
async createCategory(displayName, color = 'preset0') {
try {
return await this._request('/me/outlook/masterCategories', {
method: 'POST',
body: JSON.stringify({ displayName, color }),
})
} catch (error) {
// Category might already exist
log.warn(`Kategorie erstellen fehlgeschlagen: ${displayName}`, { error: error.message })
return null
}
}
/**
* Create subscription for webhook notifications
* @param {string} webhookUrl - Notification URL
* @param {number} expirationMinutes - Subscription expiration (max 4230 minutes / ~3 days)
*/
async createSubscription(webhookUrl, expirationMinutes = 4230) {
const expirationDateTime = new Date(Date.now() + expirationMinutes * 60000).toISOString()
return this._request('/subscriptions', {
method: 'POST',
body: JSON.stringify({
changeType: 'created,updated',
notificationUrl: webhookUrl,
resource: 'me/mailFolders(\'inbox\')/messages',
expirationDateTime,
clientState: 'email-sorter-webhook',
}),
})
}
/**
* Renew subscription
* @param {string} subscriptionId - Subscription ID
* @param {number} expirationMinutes - New expiration time
*/
async renewSubscription(subscriptionId, expirationMinutes = 4230) {
const expirationDateTime = new Date(Date.now() + expirationMinutes * 60000).toISOString()
return this._request(`/subscriptions/${subscriptionId}`, {
method: 'PATCH',
body: JSON.stringify({ expirationDateTime }),
})
}
/**
* Delete subscription
* @param {string} subscriptionId - Subscription ID
*/
async deleteSubscription(subscriptionId) {
return this._request(`/subscriptions/${subscriptionId}`, {
method: 'DELETE',
})
}
}
export default OutlookService