During my internship with CrowdStrike last summer, I was introduced to Hubot. Hubot is fun, in on all of the jokes, and exceedingly helpful. Between the automation of menial tasks and joke/novelty functions, our office chatbot was a mainstay in most threads of import.
Seeing an opportutnity for a lot of novelty and a little automation, I desperately wanted my own personal chatbot - but I did not belong to any personal slacks or hipchats to put one in. The only messaging service I used on a regular basis was texting, so I set about constructing a framework for reading and writing iMessages.
A chatbot must be able to listen for incoming messages process them. Since apple does not allow iOS apps to access messages stored on an iPhone, the easiest way to field messages was to read the synced messages stored on my computer.
If you have iMessages synced to your Mac, all of the messages are stored in a SQLite database located at ~/Library/Messages/chat.db
There is some work that has already been done with regards to reverse engineering the database structure and reading messages. The most helpful resource I found was pymessage-lite, which laid out the database structure*:
_SqliteDatabaseProperties deleted_messages
attachment handle
chat message
chat_handle_join message_attachment_join
chat_message_join
The contents of these tables should be fairly self-explanatory.
attachment
keeps track of any attachments (files, images, audio clips) sent or received, including paths to where they are stored locally as well as their file format.handle
keeps track of all known recipients (people with whom you previously exchanged iMessages).chat
keeps track of your conversation threads.message
keeps track of all messages along with their text contents, date, and the ID of the recipient.*Credit to Matt Rajca
To read messages and respond to them, the pertinent information is the contents of the message, stored in message
, and which conversation it came from, which is stored in chat
. The two are linked by chat_handle_join
.
I use watchdog to monitor the database for file changes (new messages) and the sqlite3
python package to get the contents the new messages. Here is an excerpt from my iMessage processing library:
LAST_READ = -1
# Fetches all messages exchanged with a given recipient.
def get_last_message():
global LAST_READ # highest index message that has been read
connection = _new_connection() # sqlite3 connection
c = connection.cursor() # sqlite3 cursor
text = ''
row_id = ''
date = ''
if LAST_READ == -1: # if chatbot just booted, set to current max
c.execute("SELECT * FROM message WHERE ROWID = (SELECT MAX(ROWID) FROM message)")
else: # otherwise get all new messages
c.execute("SELECT * FROM message WHERE ROWID > " + str(LAST_READ))
messages = []
for row in c:
row_id = row[0]
text = row[2]
if text is None:
continue # ignore empty messages like images
date = datetime.datetime.now()
encoded_text = text.encode('ascii', 'ignore')
message = Message(encoded_text, date) # Message datastructure to keep time and message
guid = id_to_guid(row_id) # id_to_guid(row_id) is a similar method using
LAST_READ = row_id # `chat_message_join` to retrieve the number from `chat
messages.append([message, guid])
return(messages)
connection.close()
The full file can be found HERE
Once all of the messages are in a neat array, a chatbot can use any number of language processing tools to consume commands and run the appropriate python code. But, how does it respond to those commands in iMessage?
There is no iMessage api to send messages through python, nor is there one for any language except for applescript. Thankfully, there is a commandline tool, osacscript, which allows users to write and run arbitrary applescript commands. I used the os
package in python to pipe the command into bash to run it. I based my command off of this stackoverflow question, since it was the only command I could find which allowed sending messages to named groups.
Implementation:
#takes a message to send(string) and an imessage chat id to send it to(guid)
def send_message(self, string, guid):
string = string.replace("'", "") #remove quotes in message due to
string = string.replace('"', '') #inability to escape them in command.
body = """
osascript -e 'tell application "Messages"
set myid to "%s"
set mymessage to "%s"
set theBuddy to a reference to text chat id myid
send mymessage to theBuddy
end tell' """ % (guid, string)
print(body)
os.system(body)
And there you have it! Between reading and sending iMessages, a chatbot can now be created!
See my full implementation HERE