reminders.ts•10.1 kB
import { run } from "@jxa/run";
// Define types for our reminders
interface ReminderList {
name: string;
id: string;
}
interface Reminder {
name: string;
id: string;
body: string;
completed: boolean;
dueDate: string | null;
listName: string;
completionDate?: string | null;
creationDate?: string | null;
modificationDate?: string | null;
remindMeDate?: string | null;
priority?: number;
}
/**
* Get all reminder lists
* @returns Array of reminder lists with their names and IDs
*/
async function getAllLists(): Promise<ReminderList[]> {
const lists = await run(() => {
const Reminders = Application("Reminders");
const lists = Reminders.lists();
return lists.map((list: any) => ({
name: list.name(),
id: list.id(),
}));
});
return lists as ReminderList[];
}
/**
* Get reminders from a specific list by ID with customizable properties
* @param listId ID of the list to get reminders from
* @param props Array of properties to include (optional)
* @returns Array of reminders with specified properties
*/
async function getRemindersFromListById(
listId: string,
props?: string[]
): Promise<any[]> {
return await run(
(args: { id: string; props?: string[] }) => {
function main() {
const reminders = Application("Reminders");
const list = reminders.lists.byId(args.id).reminders;
const props = args.props || [
"name",
"body",
"id",
"completed",
"completionDate",
"creationDate",
"dueDate",
"modificationDate",
"remindMeDate",
"priority",
];
// We could traverse all reminders and for each one get the all the props.
// This is more inefficient than calling '.name()' on the very reminder list. It requires
// less function calls.
const propFns: Record<string, any[]> = props.reduce(
(obj: Record<string, any[]>, prop: string) => {
obj[prop] = list[prop]();
return obj;
},
{}
);
const finalList = [];
// Flatten the object {name: string[], id: string[]} to an array of form
// [{name: string, id: string}, ..., {name: string, id: string}] which represents the list
// of reminders
for (let i = 0; i < (propFns.name?.length || 0); i++) {
const reminder = props.reduce(
(obj: Record<string, any>, prop: string) => {
obj[prop] = propFns[prop][i];
return obj;
},
{}
);
finalList.push(reminder);
}
return finalList;
}
return main();
},
{ id: listId, props }
);
}
/**
* Get all reminders from a specific list or all lists
* @param listName Optional list name to filter by
* @returns Array of reminders
*/
async function getAllReminders(listName?: string): Promise<Reminder[]> {
const reminders = await run((listName: string | undefined) => {
const Reminders = Application("Reminders");
let allReminders: Reminder[] = [];
if (listName) {
// Get reminders from a specific list
const lists = Reminders.lists.whose({ name: listName })();
if (lists.length > 0) {
const list = lists[0];
allReminders = list.reminders().map((reminder: any) => ({
name: reminder.name(),
id: reminder.id(),
body: reminder.body() || "",
completed: reminder.completed(),
dueDate: reminder.dueDate() ? reminder.dueDate().toISOString() : null,
listName: list.name(),
}));
}
} else {
// Get reminders from all lists
const lists = Reminders.lists();
for (const list of lists) {
const remindersInList = list.reminders().map((reminder: any) => ({
name: reminder.name(),
id: reminder.id(),
body: reminder.body() || "",
completed: reminder.completed(),
dueDate: reminder.dueDate() ? reminder.dueDate().toISOString() : null,
listName: list.name(),
}));
allReminders = allReminders.concat(remindersInList);
}
}
return allReminders;
}, listName);
return reminders as Reminder[];
}
/**
* Search for reminders by text
* @param searchText Text to search for in reminder names or notes
* @returns Array of matching reminders
*/
async function searchReminders(searchText: string): Promise<Reminder[]> {
const reminders = await run((searchText: string) => {
const Reminders = Application("Reminders");
const lists = Reminders.lists();
let matchingReminders: Reminder[] = [];
for (const list of lists) {
// Search in reminder names and bodies
const remindersInList = list.reminders.whose({
_or: [
{ name: { _contains: searchText } },
{ body: { _contains: searchText } },
],
})();
if (remindersInList.length > 0) {
const mappedReminders = remindersInList.map((reminder: any) => ({
name: reminder.name(),
id: reminder.id(),
body: reminder.body() || "",
completed: reminder.completed(),
dueDate: reminder.dueDate() ? reminder.dueDate().toISOString() : null,
listName: list.name(),
}));
matchingReminders = matchingReminders.concat(mappedReminders);
}
}
return matchingReminders;
}, searchText);
return reminders as Reminder[];
}
/**
* Create a new reminder
* @param name Name of the reminder
* @param listName Name of the list to add the reminder to (creates if doesn't exist)
* @param notes Optional notes for the reminder
* @param dueDate Optional due date for the reminder (ISO string)
* @returns The created reminder
*/
async function createReminder(
name: string,
listName: string = "Reminders",
notes?: string,
dueDateString?: string // Rename parameter to avoid confusion
): Promise<Reminder> {
// Convert ISO string to a simple date string that JXA can handle
let dueDateForJXA: string | undefined;
if (dueDateString) {
try {
// Parse the date string
const jsDate = new Date(dueDateString);
// Check if the date is valid
if (isNaN(jsDate.getTime())) {
throw new Error(`Invalid date string provided: ${dueDateString}`);
}
// Format the date in a way that JXA can understand
// Using a simple format: YYYY-MM-DD HH:MM:SS
const year = jsDate.getFullYear();
const month = String(jsDate.getMonth() + 1).padStart(2, '0');
const day = String(jsDate.getDate()).padStart(2, '0');
const hours = String(jsDate.getHours()).padStart(2, '0');
const minutes = String(jsDate.getMinutes()).padStart(2, '0');
const seconds = String(jsDate.getSeconds()).padStart(2, '0');
dueDateForJXA = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} catch (e) {
throw new Error(
`Invalid due date format: ${dueDateString}. ` +
`Please use ISO 8601 format (e.g., YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD).`
);
}
}
const result = await run(
(
name: string,
listName: string,
notes: string | undefined,
dueDateString: string | undefined // Pass date as a string instead of Date object
) => {
const Reminders = Application("Reminders");
// Find or create the list
let list;
const existingLists = Reminders.lists.whose({ name: listName })();
if (existingLists.length > 0) {
list = existingLists[0];
} else {
// Create a new list if it doesn't exist
list = Reminders.make({
new: "list",
withProperties: { name: listName },
});
}
// Create the reminder properties
const reminderProps: any = {
name: name,
};
if (notes) {
reminderProps.body = notes;
}
// Use the passed date string with additional error handling
if (dueDateString) {
try {
// Convert the string to a Date object in JXA context
reminderProps.dueDate = new Date(dueDateString);
} catch (e) {
// Continue without the due date rather than failing completely
}
}
// Create the reminder
const newReminder = list.make({
new: "reminder",
withProperties: reminderProps,
});
return {
name: newReminder.name(),
id: newReminder.id(),
body: newReminder.body() || "",
completed: newReminder.completed(),
dueDate: newReminder.dueDate()
? newReminder.dueDate().toISOString()
: null,
listName: list.name(),
};
},
name,
listName,
notes,
dueDateForJXA // Pass the formatted date string
);
return result as Reminder;
}
interface OpenReminderResult {
success: boolean;
message: string;
reminder?: Reminder;
}
/**
* Open the Reminders app and show a specific reminder
* @param searchText Text to search for in reminder names or notes
* @returns Result of the operation
*/
async function openReminder(searchText: string): Promise<OpenReminderResult> {
// First search for the reminder
const matchingReminders = await searchReminders(searchText);
if (matchingReminders.length === 0) {
return { success: false, message: "No matching reminders found" };
}
// Open the first matching reminder
const reminder = matchingReminders[0];
await run((reminderId: string) => {
const Reminders = Application("Reminders");
Reminders.activate();
// Try to show the reminder
// Note: This is a best effort as there's no direct way to show a specific reminder
// We'll just open the app and return the reminder details
return true;
}, reminder.id);
return {
success: true,
message: "Reminders app opened",
reminder,
};
}
export default {
getAllLists,
getAllReminders,
searchReminders,
createReminder,
openReminder,
getRemindersFromListById,
};