如何 cron 访问提醒的 AppleScript带参数 玩具 1玩具 2玩具 3玩具 4玩具 1 的修改玩具 5 (synchRemindersTest5.scpt)

问题描述

我编写了一个 AppleScript 来同步我的提醒(通过导出到 JSON)。它运行良好......来自脚本编辑器。当我尝试通过 osascript 在命令行上运行它时,我发现它在尝试访问提醒时碰壁了。大约一分半钟后,我收到此错误

/Users/robleach/Temporary/synchRemindersTest.scpt: execution error: Reminders got an error: AppleEvent timed out. (-1712)

我也在控制台中注意到了这些错误

error   19:33:49.628309-0400    tccd    Refusing client without path (from responsibility_get_responsible_audit_token_for_audit_token) PID[1422]: (#3) No such process
error   19:33:49.628370-0400    tccd    Refusing TCCAccessRequest for service kTCCServiceReminders from invalid client with pid 1422

假设这是一个权限问题,我查看了系统偏好设置>安全与隐私>提醒,并注意到 osascript 不存在,也没有 ± 按钮来添加它,即使在身份验证后也是如此。

>

我想知道将脚本保存为应用程序是否会提示安全性内容提示我启用它 - 我可以启动该应用程序,但如果我这样做,我将无法传递参数到脚本(或者至少,我不知道该怎么做)。另外,我宁愿一切都发生在后台,没有停靠栏图标或任何东西(除了需要打开提醒应用程序)。

我写了一个产生超时错误的脚本的玩具示例:

玩具 1

tell application "Reminders"
    return (properties of every reminder whose completed is false)
end tell

我这样称呼它:

> osascript /Users/robleach/Temporary/synchRemindersTest.scpt

有没有办法允许脚本的 osascript 运行被允许访问提醒?我可以喜欢对命令行可执行文件或其他东西进行代码签名吗?如果我用另一种语言写这个,会不会有同样的问题?

我正在运行 Catalina 10.15.7。

更新 1

我在控制台中挖掘了更多内容。还有许多其他可能相关的错误。我认为它实际上超时。当我在脚本编辑器中运行它时,它运行了大约 40 秒,但是当我通过 osascript 运行它时它会超时(大约一分半钟)。

但是,我记得我有一个访问提醒的 cron 作业的脚本,我不记得它有问题。所以我测试了它,无论出于何种原因,它执行了一个非常相似的命令,但成功了。它在编辑器中的运行速度比在 osascript 的命令行中运行得快得多。我从成功的脚本中取出一行代码并将其包装在一个玩具脚本中:

玩具 2

tell application "Reminders"
    set theList to "Todo Home Recurring"
    set namesDates to {name,due date} of (every reminder in list theList whose completed is false)
    display dialog "Got " & (count of namesDates) & " reminder names & dates"
end tell

并且它通过 osascript 失败并出现相同的超时错误。然后我又拉出一条线,把它加到玩具上:

玩具 3

tell application "Reminders"
    set theList to "Todo Home Recurring"
    show list theList
    delay 0.25
    set namesDates to {name,due date} of (every reminder in list theList whose completed is false)
    tell application "System Events" to display dialog "Got " & (count of namesDates) & " reminder names & dates"
end tell

...它成功。所以我认为这不再是一个权限问题。感觉这可能与 osascript 访问 Reminders 的效率有关。

我也开始注意到,当从脚本编辑器运行时,我上面的第一个玩具示例有时会失败。我不断重试以获取上面粘贴的运行时间,然后我开始感觉到一种模式。我认为,当我在提醒 GUI 中选择要查看的新列表(无论是哪一个),然后运行脚本(从编辑器)时,它就可以工作了。但是,如果我不选择一个新列表来查看并再次从编辑器中运行脚本,则会因超时而失败。

...但这似乎很疯狂。谁能解释一下这里发生了什么?

注意:我正在编写的脚本实际上是我编写的 Siri 快捷方式的 AppleScript 重写(在大约 25 秒内可靠地运行)。因为我想自动化它并且每天运行它不止一次,所以我决定使用 AppleScript。

更新 2

我尝试了@Robert Kniazidis 建议的答案。

玩具 4(玩具 1 的修改

with timeout of 3600 seconds
    tell application "Reminders"
        set allRems to (properties of every reminder whose completed is false)
        display dialog "Got " & (count of allRems) & " reminders"
    end tell
end timeout

...并密切关注控制台。

尝试 1(玩具 4)

我从 7:25:24 开始跑了 TOY 4,持续了 10 分钟,然后控制了它。我立即在控制台中看到了许多错误。我在控制台中搜索了“提醒”和 here's what I go during the run

尝试 2(玩具 4)

然后,当我在提醒 GUI 中单击列表名称时,根据我对轶事成功的见解,我尝试单击随机列表并立即再次运行 TOY 4。我在 7:38:23 开始玩玩具 4。 7:44:22,成功了!大约 6 分钟!

控制台中的消息要少得多,它们都没有标记错误。为了比较起见,here are the console results from searching for "Reminders"

讨论

我已经修改了关于正在发生的事情的理论。鉴于控制台消息,我推断当您从命令行通过 osascript 运行时,脚本被标识为“间接访问”,因此,受到更高级别的安全审查,因此需要很多 执行时间更长。也许当我“点击 GUI”(甚至通过 AppleScript,show list theList)时,安全问题仍然被认为是“间接的”,但用户并非完全不知道,因为 GUI 正在发生变化,因此受制于审查稍少,因此需要 6 分钟,而不是 10 多分钟。

如果这是真的,有趣的是,即使 Reminders GUI 位于不同的桌面上(就像我的测试中的情况*),也会应用较低级别的审查。

更新 3

今天早上我尝试了临时代码签名:

codesign --force -v -s - synchRemindersTest.app/Contents/Info.plist synchRemindersTest.app/Contents/PkgInfo synchRemindersTest.app/Contents/Resources/applet.rsrc synchRemindersTest.app/Contents/Resources/Scripts/main.scpt synchRemindersTest.app/Contents/Resources/applet.icns

...再次运行应用程序,这是玩具 1 的一个版本。仍然出现超时错误。我希望它需要 40 秒,就像从脚本编辑器运行时一样。当我有时间时,我会再试一次,但在提醒 GUI 中手动选择一个列表。

更新 4

我刚才再次运行了与更新 3 中相同的玩具。在超时前运行的 2 分钟内,控制台充满了 52,349 行 mostly this,一遍又一遍地重复,这只是该时间跨度中与搜索tccd 匹配的部分。>

我还注意到,在不同时间运行的相同未修改脚本会在某些运行中成功而在其他运行中失败。如:

玩具 5 (synchRemindersTest5.scpt)

with timeout of 600 seconds
    tell application "Reminders"
        show list "Todo Home"
        set startt to (get current date)
        set allRems to (properties of every reminder whose completed is false)
        set endt to (get current date)
        set dur to (endt - startt)
        set msg to "Got " & (count of allRems) & " reminders in " & dur & " seconds"
        tell application "System Events" to display dialog msg giving up after 5
        return msg
    end tell
end timeout

我昨天反复运行,成功,但今天运行时出现超时:

[Jun 08 22:59:51]:~/GoogleDrive/Scripts>osascript synchRemindersTest5.scpt
Got 166 reminders in 287 seconds
[Jun 08 23:06:17]:~/GoogleDrive/Scripts>osascript synchRemindersTest5.scpt
Got 166 reminders in 291 seconds
[Jun 08 23:11:45]:~/GoogleDrive/Scripts>osascript synchRemindersTest5.scpt
Got 166 reminders in 293 seconds
[Jun 08 23:17:46]:~/GoogleDrive/Scripts>osascript synchRemindersTest5.scpt
Got 166 reminders in 300 seconds
[Jun 09 8:23:28]:~/GoogleDrive/Scripts>osascript synchRemindersTest5.scpt
synchRemindersTest5.scpt: execution error: Reminders got an error: AppleEvent timed out. (-1712)

脚注

* 我一直在不同桌面上使用 Reminders 应用程序有意测试我的脚本,因为我在努力中注意到 GUI 脚本总是比通过 Reminders 字典访问更快。所以我写了两种方法:GUI 和 Reminders Dict。如果打开的 Reminders 应用程序在桌面上(我一直藏在 Dock 下),GUI 就会运行。如果我们正在全屏观看 Netflix,当 GUI 位于不同的桌面上时,我可以尝试/捕捉使用较慢的 Reminders Dict 访问方法

解决方法

以 3600 秒(1 小时)的超时时间包装您的脚本。您的脚本超时,每个命令的默认时间为 2 分钟(120 秒)。所以:

with timeout of 3600 seconds -- or 600 seconds,or as you want
    tell application "Reminders"
        return (properties of every reminder whose completed is false)
    end tell
end timeout
,

再次看到您的玩具 4。 osascript 的手册页说:脚本后面的任何参数都将作为字符串列表传递给 “运行”处理程序的直接参数。所以,你的玩具 4 应该是这样的:

on run argv -- THIS
    with timeout of 3600 seconds
        tell application "Reminders"
            set allRems to (properties of every reminder whose completed is false)
            display dialog "Got " & (count of allRems) & " reminders"
        end tell
    end timeout
end run -- and THIS

我在终端中尝试了这个脚本,使用以下命令,它成功请求访问提醒,并且在授予访问权限后工作。还要注意引号:

osascript '/Users/123/Desktop/synchRemindersTest.scpt' 'output.json' 'Reminders' 'ToDo'
,

我没有弄清楚到底是什么导致了问题,但我反复尝试了相同的确切代码,结果/行为不同,显然取决于各种情况。这是我的观察。

使用任何玩具示例,有 2 种跑步行为似乎发生了变化:

  • 运行时间(我能得到的最快速度接近半分钟,但在某些情况下相同的代码可能需要 10 分钟以上 - 我控制了它们,所以我不知道它们会运行多长时间)
  • 控制台中的 tccd 和其他错误(与 Apple 的“透明度、同意和控制”机制有关 - 即导致这些访问请求弹出窗口的原因)

我尝试通过以下方式运行上面的玩具示例:

  • 来自脚本编辑器
  • 通过命令行中的 osascript
  • 重写为用于自动化的 Javascript(又名“JXA”)(来自脚本编辑器)
  • 作为应用,双击
  • 作为从命令行打开的应用

并且我在以下各种情况下(在可能的情况下)运行了这些各种方法:

  • 解锁屏幕后立即
  • 在当前桌面上打开提醒应用
  • 在当前桌面之外打开提醒应用
  • 在运行之前无需手动与提醒 GUI 交互
  • 在运行前手动与提醒 GUI 交互
  • 包括在提醒 GUI 中显示列表的 applescript 指令
  • 不包含在提醒 GUI 中显示列表的 AppleScript 指令

还有一个重要的因素需要考虑:

  • 提醒数据库大小

Apple 实际上从未从提醒数据库中删除任何内容。我目前有 9,604 个已完成的提醒和 193 个未完成的提醒。在探索这个问题时,我在我的提醒数据库中发现了十多年以前的提醒。

我怀疑这些问题更多地与数据库的大小有关,而不是 tccd 错误,因为我在 Apple Developers 论坛上发现了一些将这些错误描述为仅仅是日志噪音的帖子。我还发现了一些开发人员的帖子,他们指出提醒数据库的不断增长会导致性能问题日益严重,并指出没有办法真正删除条目。已删除的条目被简单地标记为已删除。

我发现没有可靠的运行上下文可以在任何情况下(当您拥有大型 Reminders 数据库时)都运行得既快速又无错误。在某些情况下,所有的执行方式都会失败。有些案例运行得比其他案例快,但没有一个案例在我认为合理的运行时间内运行。

我尝试对玩具脚本的应用程序版本进行代码签名,明确授予提醒数据的权利,但根据名为 Taccy 的应用程序,虽然我可以从应用它们的文件中检索这些权利,他们没有阻止 tccd 错误或使任何案例运行得更快。我什至尝试对 osascript 可执行文件的副本进行代码签名,但显然它仅适用于应用程序包。

虽然我可以在某些情况下看到运行时的差异,并且可以通过在某些情况下以某种方式做事来避免 tccd 错误(所有似乎都需要真正的手动操作),但运行时从未显着改进和错误/失败似乎是不可避免的,例如,在屏幕被锁定的情况下。

所以我得出结论,考虑到我的提醒数据库的大小以及我想在屏幕锁定的情况下运行这个脚本(例如在一个 cron 作业中),我不得不放弃 AppleScript 解决方案。不可能以可预测和可靠的方式做到这一点。 (我曾在 iOS 设备上简单地探索过 Siri 自动化,但发现为了让它每天运行不止一次而跳过的箍太烦人了。)

记住提醒(/曾经)作为 ics 文件存储在 Library 文件夹中。我了解到,随着 iOS 13 和 macOS Catalina 中的提醒更新,提醒存储已移至 ~/Library/Reminders/Container_v1/Stores 下的 sqlite 数据库。

昨晚我在数据库中翻了翻,开始想办法。我用谷歌搜索了一些我发现的东西,并发现了一个带有 github repoalready worked the difficult sqlite stuff out。我最终得到了一个 shell 脚本,它可靠地在大约 1 秒内检索所有提醒数据(近 10k 条记录)!

我尚未对其进行改进以将其转换为 JSON 并额外检索特定日期后修改的任何内容,但到目前为止我所拥有的内容足以回答这个问题。

我用 tcsh 的(不受欢迎的)shell 语言编写了 shell 脚本。随意在 bash 中重写它或从 repo I found 开始,它已经在 bash 中(但不会检索所有提醒数据):

set REMINDERS_STORES="$HOME/Library/Reminders/Container_v1/Stores";
set SQL_GET_Z_ENT="SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'REMCDList'";

foreach DBFILE ( "$REMINDERS_STORES"/Data-*-*.sqlite )
  set DB="file:${DBFILE}?mode=ro"
  set COUNT=`sqlite3 "$DB" "SELECT COUNT(*) FROM ZREMCDOBJECT WHERE Z_ENT = ($SQL_GET_Z_ENT) AND ZCKIDENTIFIER IS NOT NULL;"`
  if ( "$COUNT" > 0 ) then
    set REMINDERS_DB="$DB"
  endif
end

set Z_ENT_LISTS=`sqlite3 "$REMINDERS_DB" "$SQL_GET_Z_ENT;"`
set YEARZERO=`date -j -f "%Y-%m-%d %H:%M:%S %z" "2001-01-01 0:0:0 +0000" "+%s"`
set NOW=`date "+%s"`

sqlite3 "$REMINDERS_DB" "SELECT strftime('%Y-%m-%dT%H:%M:%S',($YEARZERO + TASK.ZDUEDATE),'unixepoch') as dueDate,TASK.ZPRIORITY AS priority,TASK.ZTITLE1 AS title,LIST.ZNAME1 AS list,TASK.ZNOTES AS notes,TASK.ZCOMPLETED as completed,strftime('%Y-%m-%dT%H:%M:%S',($YEARZERO + TASK.ZCOMPLETIONDATE),'unixepoch') as completionDate,($YEARZERO + TASK.ZCREATIONDATE),'unixepoch') as creationDate,TASK.ZDISPLAYDATEISALLDAY as isAllday,($YEARZERO + TASK.ZDISPLAYDATEDATE),'unixepoch') as alldayDate,($YEARZERO + TASK.ZLASTMODIFIEDDATE),'unixepoch') as modificationDate,TASK.ZFLAGGED as flagged FROM ZREMCDOBJECT TASK LEFT JOIN ZREMCDOBJECT LIST on TASK.ZLIST = LIST.Z_PK WHERE LIST.Z_ENT = $Z_ENT_LISTS AND LIST.ZMARKEDFORDELETION = 0 AND TASK.ZMARKEDFORDELETION = 0 ORDER BY CASE WHEN TASK.ZDUEDATE IS NULL THEN 1 ELSE 0 END,TASK.ZDUEDATE,TASK.ZPRIORITY;"

以下是输出示例:

2011-11-01T18:30:00|0|Pay the rent|ToDo Home Recurring||1|2011-11-03T13:21:00|2017-09-18T16:59:00|0|2011-11-01T22:30:00|2020-01-04T20:40:00|0
2011-11-05T15:45:00|0|Feed meter|Reminders||1|2011-11-06T15:39:00|2017-09-18T16:59:00|0|2011-11-05T19:45:00|2020-01-04T20:36:00|0

注意,要获取您所在时区的日期,而不是 GMT(/UTC?),请附加“localtime”,例如:

strftime('%Y-%m-%dT%H:%M:%S','unixepoch','localtime')

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...