Initial Commit

This commit is contained in:
Autumn Naber
2021-01-07 21:44:24 -08:00
commit 289f44e2e1
6 changed files with 612 additions and 0 deletions

302
chatterbox.php Normal file
View File

@@ -0,0 +1,302 @@
<?php
class ChatterBox {
const HANDLE_ID = 'Handle';
const DATE_ID = 'Date';
const TEXT_ID = 'Text';
const CHAT_ID = 'Chat';
const ISSENT_ID = 'IsSent';
const FIRST_ID = "FirstName";
const LAST_ID = "LastName";
const CHAT_DB = __ROOT__.'/chat.db';
const ADDR_DB = __ROOT__.'/AddressBook-v22.abcddb';
/* TODO: IMPORTANT: Replace these with your user's messages and address book files */
const CHAT_DB_SRC = '/Users/<USERNAME>/Library/Messages/chat.db';
const ADDR_DB_SRC = '/Users/<USERNAME>/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 '<div id=' . $chat_id . ' class="chat_list';
//if($index == $this->selected_index) {
if($chat_id == $this->selected_chat) {
echo" active_chat";
}
echo '"><h5>';
echo $this->get_names_from_chat($chat_id);
//echo $chat_db->get_chat_handles($chat_list[$index][ChatterBox::CHAT_ID]);
echo '<span class="chat_date">';
echo $this->convert_epoch_to_date($chat_list[$index][ChatterBox::DATE_ID]);
echo '</span></h5><p>';
echo $chat_list[$index][ChatterBox::TEXT_ID];
echo '</p></div>';
$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 '<div class="sent_message"><p>';
} else {
echo '<div class="received_message"><p>';
}
echo $messages[$index][ChatterBox::TEXT_ID];
echo '</p><span class="time_date">';
echo $this->convert_epoch_to_datetime(
$messages[$index][ChatterBox::DATE_ID]);
echo '</span></div>';
}
}
}
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;
}
}
}
?>