簡體   English   中英

如何 cron 訪問提醒的 AppleScript(帶參數)

[英]How to cron an AppleScript (with arguments) that accesses Reminders

我寫了一個 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不存在,我也沒有±按鈕來添加它,即使在身份驗證之后也是如此。

我想知道將腳本保存為應用程序是否會提示安全性提示我啟用它 - 我可以 cron 打開該應用程序,但如果我這樣做,我將無法將 arguments 傳遞給腳本(或者至少,我不知道該怎么做)。 另外,我寧願一切都在后台發生,沒有停靠圖標或任何東西(除了需要打開的提醒應用程序)。

我寫了一個產生超時錯誤的腳本的玩具示例:

玩具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 訪問提醒的效率有關。

我也開始注意到我上面的第一個玩具示例在從腳本編輯器運行時有時會失敗。 我不斷重試以獲得上面粘貼的運行時間,我開始感覺到一種模式。 認為當我 select 在提醒 GUI 中查看一個新列表時(無論哪個都沒有關系),然后運行腳本(從編輯器),它就可以工作。 但是,如果我沒有 select 一個新列表來再次從編輯器中查看和運行腳本,它會因超時而失敗。

...但這似乎很瘋狂。 誰能解釋這里發生了什么?

注意:我正在編寫的腳本實際上是對我編寫的 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 分鍾,然后控制它。 我立即在控制台中看到了許多錯誤。 我在控制台中搜索了“提醒”,這就是我在運行期間的 go

嘗試 2(玩具 4)

然后,鑒於我在單擊提醒 GUI 中的列表名稱時對我的軼事成功的見解,我嘗試單擊隨機列表並立即再次運行玩具 4。 我在 7:38:23 開始玩玩具 4。 7點44分22秒,成功了! 那是大約6分鍾!

控制台中的消息要少得多,沒有一個被標記為錯誤。 為了比較起見,這里是搜索“提醒”的控制台結果

討論

我已經修改了我對正在發生的事情的理論。 鑒於控制台消息,我推斷當您從命令行通過 osascript 運行時,該腳本被標識為“間接訪問”,因此受到更高級別的安全審查,因此執行時間長得多. 也許當我“在 GUI 中單擊”(甚至通過 AppleScript, show list theList 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

...然后再次運行該應用程序,這是 TOY 1 的一個版本。仍然出現超時錯誤。 我希望它需要 40 秒,就像從腳本編輯器運行時一樣。 當我有時間時,我會再試一次,但手動 select 提醒 GUI 中的列表。

更新 4

我剛才又跑了和更新 3 一樣的玩具。 在超時前運行的 2 分鍾內,控制台充滿了 52,349 行,大部分是 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)

腳注

* 我一直在故意使用不同桌面上的提醒應用程序測試我的腳本,因為我在努力中注意到 GUI 腳本總是比通過提醒字典訪問更快。 所以我寫了2個方法: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 的手冊頁說:腳本后面的任何 arguments 都將作為字符串列表傳遞給“運行”處理程序的直接參數 所以,你的 TOY 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 交互
  • 包含 applescript 指令以在提醒 GUI 中顯示列表
  • 不包括在提醒 GUI 中顯示列表的 applescript 指令

還有一個重要的因素需要考慮:

  • 提醒數據庫大小

Apple 實際上並沒有從提醒數據庫中刪除任何內容。 我目前有 9,604 個已完成的提醒和 193 個未完成的提醒。 在探索這個問題時,我在我的提醒數據庫中發現了十多年前的提醒。

我懷疑這些問題與數據庫的大小有關,而不是 tccd 錯誤,因為我在 Apple Developers 論壇上發現將這些錯誤描述為僅僅是日志噪音的線程。 我還發現了一些開發人員的帖子,他們指出提醒數據庫的規模不斷擴大會導致性能問題不斷增加,並指出沒有辦法真正刪除條目。 已刪除的條目僅標記為已刪除。

我發現沒有可靠的運行上下文可以在任何情況下都快速且無錯誤地運行(當您擁有大型提醒數據庫時)。 在某些情況下,所有的執行方式都會失敗。 有些案例比其他案例運行得更快,但沒有一個案例能在我認為合理的運行時間內運行。

我嘗試對玩具腳本的應用程序版本進行代碼簽名,明確授予提醒數據的權利,但根據名為Taccy的應用程序,雖然我可以從應用它們的文件中檢索這些權利,但它們並沒有阻止 tccd錯誤或使任何案例運行得更快。 我什至嘗試對 osascript 可執行文件的副本進行代碼簽名,但顯然它只適用於應用程序包。

雖然在某些情況下我可以看到運行時的差異,並且可以通過在某些情況下以某種方式執行操作來避免 tccd 錯誤(所有這些似乎都需要真正的手動操作),但運行時從未顯着改善,並且在以下情況下錯誤/故障似乎是不可避免的例如,屏幕被鎖定。

所以我得出的結論是,鑒於我的提醒數據庫的大小以及我想在屏幕鎖定的情況下運行這個腳本(例如在一個 cron 作業中),我不得不放棄 AppleScript 解決方案。 不可能以可預測和可靠的方式做到這一點。 (我曾在 iOS 設備上簡單地探索過 Siri 自動化,但發現為了讓它每天運行不止一次而跳過的箍太煩人了。)

因此請記住,提醒已(/曾經)作為 ics 文件存儲在 Library 文件夾中。 我了解到,隨着 iOS 13 和 macOS Catalina 中的提醒更新,提醒的存儲已移至~/Library/Reminders/Container_v1/Stores下的 sqlite 數據庫。

昨晚我在數據庫中四處尋找並開始解決問題。 我用谷歌搜索了一些我發現的東西,發現一個谷歌點擊了一個github repo ,它已經解決了困難的 sqlite 東西 我最終得到了一個 shell 腳本,它可以在大約 1 秒內可靠地檢索所有提醒數據(近 1 萬條記錄)!

我尚未對其進行改進以將其轉換為 JSON 並另外檢索在某個日期之后修改的任何內容,但到目前為止我所擁有的足以回答這個問題。

我用tcsh的(不受歡迎的) shell 語言編寫了 shell 腳本。 隨意在bash中重寫它,或者從我找到的 repo開始,它已經在 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, strftime('%Y-%m-%dT%H:%M:%S',($YEARZERO + TASK.ZCREATIONDATE),'unixepoch') as creationDate, TASK.ZDISPLAYDATEISALLDAY as isAllday, strftime('%Y-%m-%dT%H:%M:%S',($YEARZERO + TASK.ZDISPLAYDATEDATE),'unixepoch') as alldayDate, strftime('%Y-%m-%dT%H:%M:%S',($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;"

以下是 output 的示例:

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?),append 'localtime',例如:

strftime('%Y-%m-%dT%H:%M:%S',($YEARZERO + TASK.ZDUEDATE),'unixepoch', 'localtime')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM