import { exec } from 'child_process'; import { promisify } from 'util'; import { MacOSReminder } from '@/lib/types'; import { getTargetRemindersList, getEnabledRemindersLists, isListEnabled, DEBUG_CONFIG } from '@/lib/config'; const execAsync = promisify(exec); /** * Service pour récupérer les rappels macOS via AppleScript * Approche sécurisée qui utilise l'API officielle d'Apple */ export class RemindersService { /** * Récupère tous les rappels depuis l'app Rappels macOS * Utilise la configuration pour filtrer les listes autorisées */ async getAllReminders(): Promise { try { if (DEBUG_CONFIG.mockData) { console.log('🔧 Mode mock activé - utilisation des données de test'); return this.getMockReminders(); } // Récupérer uniquement les listes autorisées return await this.getRemindersFromEnabledLists(); } catch (error) { console.error('Erreur lors de la récupération des rappels:', error); return this.getMockReminders(); } } /** * Récupère les rappels uniquement des listes autorisées en configuration */ async getRemindersFromEnabledLists(): Promise { try { const reminders: MacOSReminder[] = []; const enabledLists = getEnabledRemindersLists(); console.log(`📋 Synchronisation des listes autorisées: ${enabledLists.join(', ')}`); for (const listName of enabledLists) { try { console.log(`🔄 Traitement de la liste: ${listName}`); const listReminders = await this.getRemindersFromListSimple(listName); if (listReminders.length > 0) { console.log(`✅ ${listReminders.length} rappels trouvés dans "${listName}"`); reminders.push(...listReminders); } else { console.log(`ℹ️ Aucun rappel dans "${listName}"`); } } catch (error) { console.error(`❌ Erreur pour la liste ${listName}:`, error); } } console.log(`📊 Total: ${reminders.length} rappels récupérés`); return reminders; } catch (error) { console.error('Erreur getRemindersFromEnabledLists:', error); return this.getMockReminders(); } } /** * Récupère les rappels de la liste cible principale uniquement */ async getTargetListReminders(): Promise { try { const targetList = getTargetRemindersList(); console.log(`🎯 Récupération de la liste cible: ${targetList}`); return await this.getRemindersFromListSimple(targetList); } catch (error) { console.error('Erreur getTargetListReminders:', error); return []; } } /** * Récupère les rappels d'une liste spécifique */ async getRemindersByList(listName: string): Promise { try { const script = ` tell application "Reminders" set remindersList to {} set targetList to list "${listName}" repeat with reminder in reminders of targetList set reminderRecord to {id:(id of reminder as string), title:(name of reminder), notes:(body of reminder), completed:(completed of reminder), dueDate:missing value, completionDate:missing value, priority:(priority of reminder), list:"${listName}"} -- Gérer la date d'échéance try set dueDate of reminderRecord to (due date of reminder as string) end try -- Gérer la date de completion try if completed of reminder then set completionDate of reminderRecord to (completion date of reminder as string) end if end try set end of remindersList to reminderRecord end repeat return remindersList end tell `; const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo "[]"`); return this.parseAppleScriptOutput(stdout); } catch (error) { console.error(`Erreur lors de la récupération des rappels de la liste ${listName}:`, error); return []; } } /** * Récupère la liste des listes de rappels */ async getReminderLists(): Promise { try { const script = ` tell application "Reminders" set listNames to {} repeat with reminderList in lists set end of listNames to name of reminderList end repeat return listNames end tell `; const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo ""`); // Parse la sortie AppleScript pour extraire les noms de listes const lists = stdout.trim().split(', ').filter(list => list.length > 0); return lists; } catch (error) { console.error('Erreur lors de la récupération des listes:', error); return []; } } /** * Test si l'app Rappels est accessible */ async testRemindersAccess(): Promise { try { const script = ` tell application "Reminders" return count of lists end tell `; await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null`); return true; } catch (error) { console.error('Impossible d\'accéder à l\'app Rappels:', error); return false; } } /** * Parse la sortie AppleScript en objets MacOSReminder */ private parseAppleScriptOutput(output: string): MacOSReminder[] { try { console.log('Sortie AppleScript brute:', output); // Si pas de sortie ou sortie vide, retourner tableau vide if (!output || output.trim() === '' || output.trim() === '{}') { return []; } // Pour l'instant, on utilise une approche simple avec des données réelles // TODO: Implémenter le parsing complet de la sortie AppleScript return this.getRealRemindersSimple(); } catch (error) { console.error('Erreur lors du parsing AppleScript:', error); return []; } } /** * Récupère les rappels réels avec une approche simplifiée */ private async getRealRemindersSimple(): Promise { try { const reminders: MacOSReminder[] = []; const lists = await this.getReminderLists(); for (const listName of lists.slice(0, 3)) { // Limiter à 3 listes pour commencer try { const listReminders = await this.getRemindersFromListSimple(listName); reminders.push(...listReminders); } catch (error) { console.error(`Erreur pour la liste ${listName}:`, error); } } return reminders; } catch (error) { console.error('Erreur getRealRemindersSimple:', error); return this.getMockReminders(); } } /** * Récupère les rappels d'une liste avec une approche simple */ private async getRemindersFromListSimple(listName: string): Promise { try { if (DEBUG_CONFIG.verboseLogging) { console.log(`🔍 Récupération des rappels de la liste: ${listName}`); } // Script simple pour récupérer les infos de base const script = ` tell application "Reminders" set remindersList to {} try set targetList to (first list whose name is "${listName}") repeat with r in reminders of targetList try set reminderInfo to (name of r) & "|" & (completed of r) & "|" & (priority of r) & "|" & "${listName}" set end of remindersList to reminderInfo end try end repeat on error errMsg return "ERROR: " & errMsg end try return remindersList end tell `; const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo ""`); if (DEBUG_CONFIG.logAppleScript) { console.log(`📝 Sortie AppleScript pour ${listName}:`, stdout.substring(0, 200)); } // Vérifier si il y a une erreur dans la sortie if (stdout.includes('ERROR:')) { console.error(`❌ Erreur AppleScript pour ${listName}:`, stdout); return []; } return this.parseSimpleReminderOutput(stdout, listName); } catch (error) { console.error(`❌ Erreur getRemindersFromListSimple pour ${listName}:`, error); return []; } } /** * Parse la sortie simple des rappels */ private parseSimpleReminderOutput(output: string, listName: string): MacOSReminder[] { try { if (!output || output.trim() === '') return []; // Nettoyer la sortie AppleScript const cleanOutput = output.trim().replace(/^{|}$/g, ''); if (!cleanOutput) return []; const reminderStrings = cleanOutput.split(', '); const reminders: MacOSReminder[] = []; for (let i = 0; i < reminderStrings.length; i++) { const reminderStr = reminderStrings[i].replace(/"/g, ''); const parts = reminderStr.split('|'); if (parts.length >= 4) { const [title, completed, priority, list] = parts; reminders.push({ id: `${listName}-${i}`, title: title.trim(), completed: completed.trim() === 'true', priority: parseInt(priority.trim()) || 0, list: list.trim(), tags: this.extractTagsFromTitle(title.trim()) }); } } return reminders; } catch (error) { console.error('Erreur parseSimpleReminderOutput:', error); return []; } } /** * Extrait les tags du titre (format #tag) */ private extractTagsFromTitle(title: string): string[] { const tagRegex = /#(\w+)/g; const tags: string[] = []; let match; while ((match = tagRegex.exec(title)) !== null) { tags.push(match[1]); } return tags; } /** * Données de test pour le développement */ private getMockReminders(): MacOSReminder[] { return [ { id: 'mock-1', title: 'Finir le service reminders', notes: 'Implémenter la récupération des rappels macOS', completed: false, dueDate: new Date('2025-01-16'), priority: 5, list: 'Travail', tags: ['dev', 'backend'] }, { id: 'mock-2', title: 'Tester l\'intégration Jira', notes: 'Configurer l\'API Jira pour récupérer les tâches', completed: false, dueDate: new Date('2025-01-18'), priority: 9, list: 'Projets', tags: ['jira', 'api'] }, { id: 'mock-3', title: 'Créer le Kanban board', completed: true, completionDate: new Date('2025-01-10'), priority: 5, list: 'Travail', tags: ['ui', 'frontend'] } ]; } } // Instance singleton export const remindersService = new RemindersService();