Reading iMessages with AppleScript
Asked Answered
C

1

10

I'm trying to write a script that will go through all the chats in the Messages app - my goal is to find all messages I haven't replied to and send me an reminder.

But I'm stuck at square one - I can see how many message I have:

tell application "Messages" to log (count of chats)

But I can't get even simple properties out of a chat; for example:

tell application "Messages" to set x to started of first chat

gives the error: Can’t get started of chat 1." number -1728 from started of chat 1

Any ideas where I'm going wrong...?

Clipped answered 3/1, 2018 at 22:49 Comment(3)
Decided to give up on this and go directly to the database with a shell script. If it helps anyone, I think this will do the job: sqlite3 -line ~/Library/Messages/chat.db "SELECT m.ROWID, text, MAX(date) lastMessageDate, is_from_me, h.id FROM message m INNER JOIN handle h ON h.ROWID=m.handle_id GROUP BY h.ROWID"Clipped
Indeed, AppleScript doesn't do much with Messages currently. Given the data returned from your SQL query, what would you like do with it specifically ? Would you just like a list containing each phone number/iMessage address and their last reply, or would you like something done with this list ?Weyermann
At least as of Messages 14.0 on Monterey, ~/Library/Messages exists, and "file" says it's a directory, but I can't "ls" it even with sudo: Operation not permitted. Maybe they moved it?Quintillion
W
6

I used your sqlite3 code with a few minor tweaks, then created this AppleScript, which generates a list of reminders (in the Reminders app). The only downside is that the senders appear as phone numbers or iMessage addresses, and don't get "converted" to names using your address book. This might be possible to achieve through some tell commands to Contacts and doing a search against phone numbers, but that's beyond the scope of this particular endeavour for now.

    -- Run an SQL query on the messages database via shell script
    do shell script (["sqlite3 -line ~/Library/Messages/chat.db ", ¬
        "'SELECT MAX(date) lastMessageDate, h.id, text ", ¬
        "FROM message m INNER JOIN handle h ON h.ROWID=m.handle_id ", ¬
        "WHERE is_from_me = 0 GROUP BY h.ROWID' | egrep -io -e '\\w+ = .+'"] as text)
    
    set query_response to result
        --> lastMessageDate = %timestamp%
        --> id = %phone number or iMessage address%
        --> text = %message content%
        --> ...
    
    -- Groups all messages into a single item of an array
    -- Each occurence of  "lastMessageDate = " indicates 
    -- a new message and, hence, a new array item.
    set AppleScript's text item delimiters to "lastMessageDate = "
    set query_response to rest of text items of query_response
    
    -- Loop through the array organise each message
    repeat with message in query_response
        
        -- Split the array item up into its components
        set [receivedOn, sentFrom, textContent] ¬
            to [paragraph 1, ¬
            text 6 thru -1 of paragraph 2, ¬
            text 8 thru -1 of paragraph 3] of message
        
        -- Convert ReceivedOn to a value representing
        -- the number of days since 01/01/2001
        set receivedOn to receivedOn / (1.0E+9 * 86400)
        
        -- Convert ReceivedOn again to the date
        -- corresponding to the number of days since
        -- 01/01/2001, i.e. the date and time the message
        -- was sent
        set receivedOn to (date "Monday, 1 January 2001 at 00:00:00") + receivedOn * days
        
        -- Make a reminder
        tell application "Reminders"
            if not (exists (list named "Awaiting My Reply")) then ¬
                make new list with properties {name:"Awaiting My Reply"}
            
            tell list named "Awaiting My Reply"
                -- This creates a reminder with a past due date
                -- meaning you can sort the reminder list by due
                -- date to see who has been waiting longest
                -- for you to reply
                make new reminder with properties ¬
                    {name:"Reply To " & sentFrom, body:¬
                        textContent, due date:¬
                        receivedOn} ¬
                        
            end tell
        end tell
    end repeat

Of course, if any of your text messages happen to contain the phrase "lastMessagedate = ", then that will not work well with the script above. However, there are workarounds for this, and I experimented with various formats returned by sqlite3 including list, column, and csv, all of which have their advantages and disadvantages.

ADDENDUM: Retrieving contact details from a matching phone number

After much experimentation, have managed to integrate the above script with an address book search to replace phone numbers with contacts' names:

    -- Replace dbID with your address book's serial number
    -- (look in ~/Library/Application Support/AddressBook/Sources)
    property dbID : "377FDA5C-013A-430A-A964-9943C0B40137"
    property db : {Messages:"/Users/CK/Library/Messages/chat.db", Addresses:"/Users/CK/Library/Application Support/AddressBook/Sources/" & dbID & "/AddressBook-v22.abcddb"}

    -- Run an SQL query on the messages database via shell script
    set Messages to do shell script (["sqlite3 -line ", ¬
        quoted form of (Messages in db) as text, ¬
        " 'SELECT MAX(date) lastMessageDate, h.id, text", ¬
        " FROM message m INNER JOIN handle h ON h.ROWID=m.handle_id", ¬
        " WHERE is_from_me = 0 GROUP BY h.ROWID'", ¬
        " | egrep -io -e '\\w+ = .+'"] as text)
    --> lastMessageDate = %timestamp%
    --> id = %phone number or iMessage address%
    --> text = %message content%
    --> ...
    
    set PhoneNumbers to do shell script (["sqlite3 -list -separator : ", ¬
        quoted form of (Addresses in db) as text, ¬
        " 'select r.ZFIRSTNAME, r.ZLASTNAME, p.ZFULLNUMBER", ¬
        " from ZABCDRECORD as r, ZABCDPHONENUMBER as p", ¬
        " WHERE p.ZOWNER=r.Z_PK'", ¬
        " | tr -c -d '[:alnum:]:[:cntrl:]:[:space:]:+#*'"] as text)
    --> %first name%:%last name:%phone number%
    --> ...
    
    set NamesAndNumbers to paragraphs of PhoneNumbers
    set AppleScript's text item delimiters to ":"
    tell NamesAndNumbers to ¬
        repeat with n from 1 to count it
            set its item n to ¬
                {firstname:text item 1, lastname:text item 2, phone:text item 3} ¬
                    of its item n
        end repeat
    
    -- Groups all messages into a single item of an array
    -- Each occurence of  "lastMessageDate = " indicates 
    -- a new message and, hence, a new array item.
    set AppleScript's text item delimiters to "lastMessageDate = "
    set Messages to rest of text items of Messages
    
    -- Loop through the array organise each message
    repeat with message in Messages
        
        -- Split the array item up into its components
        set [receivedOn, sentFrom, textContent] ¬
            to [paragraph 1, ¬
            text 6 thru -1 of paragraph 2, ¬
            text 8 thru -1 of paragraph 3] of message
        
        -- Convert ReceivedOn to a value representing
        -- the number of days since 01/01/2001
        set receivedOn to receivedOn / (1.0E+9 * 86400)
        
        -- Convert ReceivedOn again to the date
        -- corresponding to the number of days since
        -- 01/01/2001, i.e. the date and time the message
        -- was sent
        set receivedOn to (date "Monday, 1 January 2001 at 00:00:00") + receivedOn * days
        
        -- Keep the "+" delimiter but replace the "+44" with
        -- your own country's intl dialling code, e.g. {"+1", "+"}
        -- as this may or may not prefix a phone number
        set AppleScript's text item delimiters to {"+44", "+"}
        set sentFrom to last text item of sentFrom
        
        -- Find the name to whom the phone number belongs
        ignoring white space
            set match to ""
            repeat with person in NamesAndNumbers
                if the person's phone contains sentFrom then
                    set match to contents of the person
                    exit repeat
                end if
            end repeat
        end ignoring
        
        set AppleScript's text item delimiters to space
        if match is not "" then set sentFrom to the contents of ¬
            {firstname, lastname} of match as text
        
        -- Make a reminder
        tell application "Reminders"
            if not (exists (list named "Awaiting My Reply")) then ¬
                make new list with properties {name:"Awaiting My Reply"}
            
            tell list named "Awaiting My Reply"
                -- This creates a reminder with a past due date
                -- meaning you can sort the reminder list by due
                -- date to see who has been waiting longest
                -- for you to reply
                make new reminder with properties ¬
                    {name:"Reply To " & sentFrom, body:¬
                        textContent, due date:¬
                        receivedOn} ¬
                        
            end tell
        end tell
    end repeat
Weyermann answered 5/1, 2018 at 1:10 Comment(7)
Thats great thanks - linking to Contacts is the next big challenge :)Clipped
I thought something like tell application "Contacts" to get name of every person whose value of phone 1 contains "%phone number%" might work, but it's very, very fussy in how it matches. I can get it match individual people if I already know the format of the number in advance, etc. Otherwise, I can get a list of people using a very broad number match of only 3 or so digits.Weyermann
I've used another SQL query to access the contacts database and retrieve phone numbers from there. I had to find where my relevant contacts' database was stored, and found it at ~/Library/Application\ Support/AddressBook/Sources/%SERIALNUMBER%/AddressBook-v22.abcddb. The relevant sqlite3 query to retrieve names and phone numbers is select r.ZFIRSTNAME,r.ZLASTNAME,p.ZFULLNUMBER from ZABCDRECORD as r,ZABCDPHONENUMBER as p WHERE p.ZOWNER = r.Z_PK and I found it most useful to return it as a -list. I'll work up an AppleScript to integrate it with above and update my answer when I do.Weyermann
Done it! Adding an addendum to my answer above.Weyermann
Got an error with set receivedOn to (date "Monday, 1 January 2001 at 00:00:00") + receivedOn * days : "Invalid date and time date Monday, 1 January 2001 at 00:00:00."Chromato
Also "Error: unable to open database "/Users/XXXXX/Library/Messages/chat.db": unable to open database file"Chromato
@JBis I’m afraid both of those errors are specific to your system and so you’ll need to find out why your database can’t be read (try accessing it from Terminal), and also what format to use when constructing AppleScript date objects, which is dependent on your system settings.Weyermann

© 2022 - 2024 — McMap. All rights reserved.