/Library/Messages/chat.db'; const ADDR_DB_SRC = '/Users//Application\ Support/AddressBook/AddressBook-v22.abcddb'; public $chat_ids; public $selected_chat; public $filewatch; function __construct() { date_default_timezone_set('America/Los_Angeles'); $this->chat_ids = ChatterBox::get_chat_list(); $this->selected_chat = $this->chat_ids[0]; // Watch the write-ahead log, since that's what stores recent changes $this->filewatch = new FileWatch(ChatterBox::CHAT_DB_SRC . '-wal'); } function __destruct() { } public function check_for_changes() { $filechanged = $this->filewatch->has_changed(); if ($filechanged) { $this->filewatch->copy_database(ChatterBox::CHAT_DB_SRC, __ROOT__ . '/'); } echo $filechanged; } public function get_chat_messages($chat_id) { $chat_db = new SQLite3(ChatterBox::CHAT_DB, SQLITE3_OPEN_READONLY); if (!$chat_db) { error_log($chat_db->lastErrorMsg()); } $query = "SELECT m.date AS " . ChatterBox::DATE_ID . ", " . "m.text AS " . ChatterBox::TEXT_ID . ", " . "m.is_from_me AS " . ChatterBox::ISSENT_ID . ", " . "h.id AS " . ChatterBox::HANDLE_ID . " " . "FROM message AS m " . "LEFT OUTER JOIN chat_message_join AS cm " . "ON cm.message_id = m.ROWID " . "LEFT OUTER JOIN handle as h " . "ON m.handle_id = h.ROWID " . "WHERE cm.chat_id = $chat_id " . "ORDER BY m.date ASC;"; $ret = $chat_db->query($query); if(!$ret) { error_log( "Unable to query database. " . $chat_db->lastErrorMsg() ); } else { while($row = $ret->fetchArray(SQLITE3_ASSOC)) { $chat_messages[] = $row; } } $chat_db->close(); return $chat_messages; } public static function get_chat_list() { $chat_ids = array(); $chat_db = new SQLite3(ChatterBox::CHAT_DB, SQLITE3_OPEN_READONLY); if (!$chat_db) { error_log($chat_db->lastErrorMsg()); return $chat_ids; } $query = "SELECT DISTINCT(cm.chat_id) AS " . ChatterBox::CHAT_ID . " " . "FROM chat_message_join AS cm " . "ORDER BY cm.message_date;"; $ret = $chat_db->query($query); if(!$ret) { error_log( "Unable to query database. " . $chat_db->lastErrorMsg() ); } else { while($row = $ret->fetchArray(SQLITE3_ASSOC)) { $chat_ids[] = $row[ChatterBox::CHAT_ID]; } } $chat_db->close(); return $chat_ids; } public function get_chat_handles($chat_id) { $handles = array(); $chat_db = new SQLite3(ChatterBox::CHAT_DB, SQLITE3_OPEN_READONLY); if (!$chat_db) { error_log($chat_db->lastErrorMsg()); return $handles; } $query = "SELECT h.id AS " . ChatterBox::HANDLE_ID . " " . "FROM chat_handle_join AS ch " . "INNER JOIN handle as h " . " on ch.handle_id = h.ROWID " . "WHERE ch.chat_id = $chat_id;"; $ret = $chat_db->query($query); if(!$ret) { error_log( "Unable to query database. " . $chat_db::lastErrorMsg() ); } else { while($row = $ret->fetchArray(SQLITE3_ASSOC)) { $handles[] = $row[ChatterBox::HANDLE_ID]; } } $chat_db->close(); return $handles; } public function get_chat_previews() { $chat_previews = array(); $chat_db = new SQLite3(ChatterBox::CHAT_DB, SQLITE3_OPEN_READONLY); if (!$chat_db) { error_log($chat_db->lastErrorMsg()); return $chat_previews; } $query = "SELECT MAX(cm.message_date) AS " . ChatterBox::DATE_ID . ", " . "cm.chat_id AS " . ChatterBox::CHAT_ID . ", " . "m.text AS " . ChatterBox::TEXT_ID . " " . "FROM chat_message_join AS cm " . "INNER JOIN message AS m " . "ON cm.message_id = m.ROWID " . "GROUP BY cm.chat_id " . "ORDER BY cm.message_date DESC;"; $this->chat_ids = array(); $ret = $chat_db->query($query); if(!$ret) { error_log( "Unable to query database. " . $chat_db->lastErrorMsg() ); } else { while($row = $ret->fetchArray(SQLITE3_ASSOC)) { $this->chat_ids[] = $row[ChatterBox::CHAT_ID]; $chat_previews[] = $row; } } $chat_db->close(); return $chat_previews; } public function convert_epoch_to_date($epoch) { return date("F d", substr($epoch, 0, -3)); } public function convert_epoch_to_datetime($epoch) { return date("H:i \| F d", substr($epoch, 0, -3)); } public function get_names_from_chat($chat_id) { $handles = $this->get_chat_handles($chat_id); $names = $this->get_names_from_handles($handles); return $names; } public function get_names_from_handles($handles) { $addr_db = new SQLite3(ChatterBox::ADDR_DB, SQLITE3_OPEN_READONLY); if (!$addr_db) { error_log($addr_db->lastErrorMsg()); } $names = array(); foreach($handles as $handle) { // Remove + from beginning of phone numbers $handle = preg_replace("/^\+/", "", $handle); $query = "SELECT r.ZFIRSTNAME AS " . ChatterBox::FIRST_ID . ", " . "r.ZLASTNAME AS " . ChatterBox::LAST_ID . " " . "FROM ZABCDRECORD AS r " . "INNER JOIN ZABCDCONTACTINDEX as i " . "ON i.ZCONTACT = r.ROWID " . "WHERE i.ZSTRINGFORINDEXING LIKE \"%" . $handle . "%\";"; $ret = $addr_db->querySingle($query, true); if($ret == false) { $names[] = $handle; } else { $names[] = $ret[ChatterBox::FIRST_ID] . " " . $ret[ChatterBox::LAST_ID]; } } $addr_db->close(); return join(", ", $names); } public function display_chat_list() { $chat_list = $this->get_chat_previews(); $chat_ids = array(); for($index = 0; $index < count($chat_list); $index++) { $chat_id = $chat_list[$index][ChatterBox::CHAT_ID]; echo '
'; echo $this->get_names_from_chat($chat_id); //echo $chat_db->get_chat_handles($chat_list[$index][ChatterBox::CHAT_ID]); echo ''; echo $this->convert_epoch_to_date($chat_list[$index][ChatterBox::DATE_ID]); echo '

'; echo $chat_list[$index][ChatterBox::TEXT_ID]; echo '

'; $chat_ids[] = $chat_id; } $this->chat_ids = $chat_ids; } public function send_message($message, $recipient) { //TODO } public function display_chat_history() { $messages = $this->get_chat_messages($this->selected_chat); for($index = 0; $index < count($messages); $index++) { if($messages[$index][ChatterBox::ISSENT_ID]) { echo '

'; } else { echo '

'; } echo $messages[$index][ChatterBox::TEXT_ID]; echo '

'; echo $this->convert_epoch_to_datetime( $messages[$index][ChatterBox::DATE_ID]); echo '
'; } } } class FileWatch { protected $watch_file = null; protected $last_accessed = 0; public function __construct($filename) { $this->watch_file = $filename; return true; } public function has_changed() { $file_changed = false; $ret = null; $output = null; // Mac-variant exec('stat -f %m ' . $this->watch_file, $output, $ret); // POSIX-variant // exec('stat -t -c %Y ' . $this->watch_file, $output, $ret); if($ret) { error_log("Unable to access file: " . $this->watch_file); } else { if(!$this->last_accessed || (int)$output[0] > $this->last_accessed) { $this->last_accessed = (int)$output[0]; $file_changed = true; } } return $file_changed; } function copy_database($source, $destination) { $db_file = basename($source); $src = dirname($source) . '/'; $dst = $destination; // Copy database file if (!copy($src . $db_file, $dst . $db_file)) { error_log('Could not copy database'); return; } // Copy write-ahead log if (!copy($src . $db_file . '-wal', $dst . $db_file . '-wal')) { error_log('Could not copy write-ahead log'); return; } } } ?>