从 AppleScript 到 Swift Playground:解决导出日历备注的问题

最近在项目中,遇到了一个问题:从 macOS 的日历应用中导出特定事件的备注内容。

使用 AppleScript

先前用的是 AppleScript:《在 Mac 上将当天的日历事项导出为 TXT 文件》,通过脚本直接访问并提取 iCloud 日历。然而, AppleScript 对于访问日历中部分事件的属性,特别是“备注”字段,无法访问,例如:

-- 设置目标日历和事件名称
set targetCalendar to "日常" -- iCloud 中的日常列表
set targetEventTitle to "A1-今日重点工作安排"

-- 获取今天的日期
set currentDate to current date
set todayStart to currentDate - (time of currentDate)
set todayEnd to todayStart + 1 * days

-- 查找日历中的事件
tell application "Calendar"
	set theCalendar to calendar targetCalendar
	set theEvents to (every event of theCalendar whose summary is targetEventTitle and start date is greater than or equal to todayStart and start date is less than todayEnd)
	
	if (count of theEvents) > 0 then
		set theEvent to item 1 of theEvents
		
		-- 初始化存储详细信息的字符串
		set eventDetails to ""
		
		-- 获取已知属性并打印
		try
			set eventDetails to eventDetails & "标题: " & (get summary of theEvent) & return
		on error
			set eventDetails to eventDetails & "标题: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "开始时间: " & (get start date of theEvent) & return
		on error
			set eventDetails to eventDetails & "开始时间: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "结束时间: " & (get end date of theEvent) & return
		on error
			set eventDetails to eventDetails & "结束时间: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "位置: " & (get location of theEvent) & return
		on error
			set eventDetails to eventDetails & "位置: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "备注: " & (get notes of theEvent) & return
		on error
			set eventDetails to eventDetails & "备注: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "是否为全天事件: " & (get allday event of theEvent) & return
		on error
			set eventDetails to eventDetails & "是否为全天事件: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "创建日期: " & (get creation date of theEvent) & return
		on error
			set eventDetails to eventDetails & "创建日期: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "最后修改日期: " & (get modification date of theEvent) & return
		on error
			set eventDetails to eventDetails & "最后修改日期: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "参与者: " & (get attendees of theEvent) & return
		on error
			set eventDetails to eventDetails & "参与者: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "事件状态: " & (get status of theEvent) & return
		on error
			set eventDetails to eventDetails & "事件状态: 无法获取" & return
		end try
		
		try
			set eventDetails to eventDetails & "URL: " & (get url of theEvent) & return
		on error
			set eventDetails to eventDetails & "URL: 无法获取" & return
		end try
		
		-- 打印所有事件详细信息到控制台
		log eventDetails
		
	else
		log "未找到今日的相关事件"
	end if
end tell

这个脚本试图通过 get notes of theEvent 获取事件备注,但在实际运行过程中,无论如何修改和调整脚本,依然无法成功获取“备注”这一字段。AppleScript 在处理日历中某些复杂的事件属性时,可能存在一些限制。

AppleScript 进一步探索的失败

我尝试了各种不同的获取方法,包括导出事件的其他属性(如标题、开始和结束时间等)。但对于最关心的“备注”内容,AppleScript 就无法获得。最终,我不得不考虑其他替代方案。

image-20240906下午73228179

转向 Swift Playground

在 AppleScript 行不通的情况下,我决定使用 Swift,在 Swift Playground 里开发和测试都挺方便。Swift 提供了对 EventKit 框架的支持,访问日历事件比较灵活和强大。以下是我使用 Swift Playground 成功提取日历事件的代码:

import EventKit
import Foundation

// 创建事件存储对象
let eventStore = EKEventStore()

// 请求访问权限
eventStore.requestAccess(to: .event) { granted, error in
    if granted {
        // 获取当前日期范围
        let now = Date()
        let calendar = Calendar.current
        let startDate = calendar.startOfDay(for: now)
        let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)!

        // 查找事件
        let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
        let events = eventStore.events(matching: predicate)
        
        // 查找目标事件
        for event in events {
            if event.title == "A1-今日重点工作安排" {
                // 打印事件详细信息
                print("标题: \(event.title ?? "无标题")")
                print("开始时间: \(event.startDate)")
                print("结束时间: \(event.endDate)")
                print("位置: \(event.location ?? "无")")
                print("备注: \(event.notes ?? "无备注")")
                // 处理 URL
                if let url = event.url {
                    print("URL: \(url)")
                } else {
                    print("URL: 无")
                }
            }
        }
    } else {
        print("访问事件存储失败: \(error?.localizedDescription ?? "未知错误")")
    }
}

通过 Swift 中的 EKEventStore,我得以轻松访问事件的所有详细信息,包括标题、开始时间、结束时间、位置以及我们之前一直无法获取的“备注”内容。

输出备注到文件

为了进一步扩展功能,我将获取到的备注信息保存到了桌面的文本文件中,方便后续查看和使用。以下是最终代码:

import EventKit
import PlaygroundSupport

// 允许 Playground 执行异步代码
PlaygroundPage.current.needsIndefiniteExecution = true

// 创建事件存储对象
let eventStore = EKEventStore()

// 请求完全访问权限
eventStore.requestFullAccessToEvents { granted, error in
    if granted {
        // 获取当前日期范围
        let now = Date()
        let calendar = Calendar.current
        let startDate = calendar.startOfDay(for: now)
        let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)!

        // 查找事件
        let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
        let events = eventStore.events(matching: predicate)
        
        // 查找目标事件
        var notesArray: [String] = []
        
        for event in events {
            if event.title == "A1-今日重点工作安排" {
                // 获取备注内容
                let notes = event.notes ?? "无备注"
                // 将备注按行拆分并添加到数组中
                let lines = notes.split(separator: "\n").map { String($0) }
                notesArray.append(contentsOf: lines)
            }
        }

        if !notesArray.isEmpty {
            // 构造标题
            let header = "共\(notesArray.count)个重点+遗留工作:\n"
            
            // 添加序号到每个事项
                        let numberedNotes = notesArray.enumerated().map { index, note in
                            let number = String(format: "%02d", index + 1)
                            return "\(number). \(note)"
                        }.joined(separator: "\n")
                        
                        // 将标题和编号内容组合
                        let combinedNotes = header + numberedNotes
            
            // 将标题和备注内容组合
            // let combinedNotes = header + notesArray.joined(separator: "\n")
            
            // 输出到控制台
            print(combinedNotes)
            
            // 获取当前日期并格式化为 YYYY-MM-DD
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            let dateString = dateFormatter.string(from: now)
                        
            // 设置文件名
            let fileName = "keytask-\(dateString).txt"
            
            // 将内容写入到桌面文件
            let fileManager = FileManager.default
            let desktopURL = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first!
            let fileURL = desktopURL.appendingPathComponent(fileName)
            
            do {
                try combinedNotes.write(to: fileURL, atomically: true, encoding: .utf8)
                print("备注内容已成功写入到文件: \(fileURL.path)")
            } catch {
                print("写入文件失败: \(error.localizedDescription)")
            }
        } else {
            print("未找到目标事件的备注内容")
        }
    } else {
        print("访问事件存储失败: \(error?.localizedDescription ?? "未知错误")")
    }

    // 完成 Playground 执行
    PlaygroundPage.current.finishExecution()
}

image-20240906下午73735483

总结

虽然 AppleScript 在处理简单的日历自动化任务时依然是一个很好的选择,但在涉及更复杂的日历事件属性时,它的局限性也显现了出来。这个案例也证明了在 macOS 上,面对复杂自动化需求时,Swift 提供了更强大的解决方案。


将本地 Windows 文件夹同步到远程服务器 Windows 批量关闭进程