- Marked tasks in TODO.md as completed for macOS reminders integration. - Enhanced RemindersService to filter reminders based on enabled lists and added detailed logging for better debugging. - Implemented methods for retrieving reminders from specific lists and parsing output from AppleScript.
356 lines
11 KiB
TypeScript
356 lines
11 KiB
TypeScript
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<MacOSReminder[]> {
|
||
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<MacOSReminder[]> {
|
||
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<MacOSReminder[]> {
|
||
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<MacOSReminder[]> {
|
||
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<string[]> {
|
||
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<boolean> {
|
||
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<MacOSReminder[]> {
|
||
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<MacOSReminder[]> {
|
||
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();
|