Skip to content

Commit

Permalink
Merge pull request #249 from ReagentX/feat/cs/use-destination-caller-id
Browse files Browse the repository at this point in the history
Feat/cs/use destination caller id
  • Loading branch information
ReagentX committed May 8, 2024
2 parents 924e802 + a7e8eff commit 88ff7cd
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 28 deletions.
3 changes: 3 additions & 0 deletions imessage-database/src/tables/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub struct Message {
pub text: Option<String>,
pub service: Option<String>,
pub handle_id: Option<i32>,
pub destination_caller_id: Option<String>,
pub subject: Option<String>,
pub date: i64,
pub date_read: i64,
Expand Down Expand Up @@ -108,6 +109,7 @@ impl Table for Message {
text: row.get("text").unwrap_or(None),
service: row.get("service").unwrap_or(None),
handle_id: row.get("handle_id").unwrap_or(None),
destination_caller_id: row.get("destination_caller_id").unwrap_or(None),
subject: row.get("subject").unwrap_or(None),
date: row.get("date")?,
date_read: row.get("date_read").unwrap_or(0),
Expand Down Expand Up @@ -966,6 +968,7 @@ mod tests {
text: None,
service: Some("iMessage".to_string()),
handle_id: Some(i32::default()),
destination_caller_id: None,
subject: None,
date: i64::default(),
date_read: i64::default(),
Expand Down
18 changes: 9 additions & 9 deletions imessage-exporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ This binary is available on [crates.io](https://crates.io/crates/imessage-export

`cargo install imessage-exporter` is the best way to install the app for normal use.

<details><summary>Uninstall steps</summary><p><pre>% cargo uninstall imessage-exporter</pre></p></details>
<details><summary>Uninstall steps</summary><p><pre>$ cargo uninstall imessage-exporter</pre></p></details>

### Homebrew

This binary is available via [`brew`](https://formulae.brew.sh/formula/imessage-exporter).

`brew install imessage-exporter` will install the app, but it may not be up to date with the latest release.

<details><summary>Uninstall steps</summary><p><pre>% brew uninstall imessage-exporter</pre></p></details>
<details><summary>Uninstall steps</summary><p><pre>$ brew uninstall imessage-exporter</pre></p></details>

### Prebuilt Binaries

The [releases page](https://github.com/ReagentX/imessage-exporter/releases) provides prebuilt binaries for both Apple Silicon and Intel-based Macs.

<details><summary>Uninstall steps</summary><p><pre>% rm path/to/imessage-exporter-binary</pre></p></details>
<details><summary>Uninstall steps</summary><p><pre>$ rm path/to/imessage-exporter-binary</pre></p></details>

### Installing manually

Expand Down Expand Up @@ -98,37 +98,37 @@ The [releases page](https://github.com/ReagentX/imessage-exporter/releases) prov
Export as `html` and copy attachments in web-compatible formats from the default iMessage Database location to your home directory:

```zsh
% imessage-exporter -f html -c compatible
$ imessage-exporter -f html -c compatible
```

Export as `txt` and copy attachments in their original formats from the default iMessage Database location to a new folder in the current working directory called `output`:

```zsh
% imessage-exporter -f txt -o output -c efficient
$ imessage-exporter -f txt -o output -c efficient
```

Export as `txt` from the an unencrypted iPhone backup located at `~/iphone_backup_latest` to a new folder in the current working directory called `backup_export`:

```zsh
% imessage-exporter -f txt -p ~/iphone_backup_latest -a iOS -o backup_export
$ imessage-exporter -f txt -p ~/iphone_backup_latest -a iOS -o backup_export
```

Export as `html` from `/Volumes/external/chat.db` to `/Volumes/external/export` without copying attachments:

```zsh
% imessage-exporter -f html -c disabled -p /Volumes/external/chat.db -o /Volumes/external/export
$ imessage-exporter -f html -c disabled -p /Volumes/external/chat.db -o /Volumes/external/export
```

Export as `html` from `/Volumes/external/chat.db` to `/Volumes/external/export` with attachments in `/Volumes/external/Attachments`:

```zsh
% imessage-exporter -f html -c efficient -p /Volumes/external/chat.db -r /Volumes/external/Attachments -o /Volumes/external/export
$ imessage-exporter -f html -c efficient -p /Volumes/external/chat.db -r /Volumes/external/Attachments -o /Volumes/external/export
```

Export messages from `2020-01-01` to `2020-12-31` as `txt` from the default macOS iMessage Database location to `~/export-2020`:

```zsh
% imessage-exporter -f txt -o ~/export-2020 -s 2020-01-01 -e 2021-01-01 -a macOS
$ imessage-exporter -f txt -o ~/export-2020 -s 2020-01-01 -e 2021-01-01 -a macOS
```

## Features
Expand Down
134 changes: 132 additions & 2 deletions imessage-exporter/src/app/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub const OPTION_DISABLE_LAZY_LOADING: &str = "no-lazy";
pub const OPTION_CUSTOM_NAME: &str = "custom-name";
pub const OPTION_PLATFORM: &str = "platform";
pub const OPTION_BYPASS_FREE_SPACE_CHECK: &str = "ignore-disk-warning";
pub const OPTION_USE_CALLER_ID: &str = "use-caller-id";

// Other CLI Text
pub const SUPPORTED_FILE_TYPES: &str = "txt, html";
Expand Down Expand Up @@ -62,6 +63,8 @@ pub struct Options {
pub no_lazy: bool,
/// Custom name for database owner in output
pub custom_name: Option<String>,
/// If true, use the database owner's caller ID instead of "Me"
pub use_caller_id: bool,
/// The database source's platform
pub platform: Platform,
/// If true, disable the free disk space check
Expand All @@ -80,6 +83,7 @@ impl Options {
let end_date: Option<&String> = args.get_one(OPTION_END_DATE);
let no_lazy = args.get_flag(OPTION_DISABLE_LAZY_LOADING);
let custom_name: Option<&String> = args.get_one(OPTION_CUSTOM_NAME);
let use_caller_id = args.get_flag(OPTION_USE_CALLER_ID);
let platform_type: Option<&String> = args.get_one(OPTION_PLATFORM);
let ignore_disk_space = args.get_flag(OPTION_BYPASS_FREE_SPACE_CHECK);

Expand Down Expand Up @@ -114,6 +118,11 @@ impl Options {
"Option {OPTION_END_DATE} is enabled, which requires `--{OPTION_EXPORT_TYPE}`"
)));
}
if use_caller_id && export_file_type.is_none() {
return Err(RuntimeError::InvalidOptions(format!(
"Option {OPTION_USE_CALLER_ID} is enabled, which requires `--{OPTION_EXPORT_TYPE}`"
)));
}

// Warn the user if they are exporting to a file type for which lazy loading has no effect
if no_lazy && export_file_type != Some(&"html".to_string()) {
Expand Down Expand Up @@ -148,6 +157,18 @@ impl Options {
"Diagnostics are enabled; {OPTION_END_DATE} is disallowed"
)));
}
if diagnostic && use_caller_id {
return Err(RuntimeError::InvalidOptions(format!(
"Diagnostics are enabled; {OPTION_USE_CALLER_ID} is disallowed"
)));
}

// Ensure that there are no custom name conflicts
if custom_name.is_some() && use_caller_id {
return Err(RuntimeError::InvalidOptions(format!(
"`--{OPTION_CUSTOM_NAME}` is enabled; `--{OPTION_USE_CALLER_ID}` is disallowed"
)));
}

// Build query context
let mut query_context = QueryContext::default();
Expand Down Expand Up @@ -217,6 +238,7 @@ impl Options {
query_context,
no_lazy,
custom_name: custom_name.cloned(),
use_caller_id,
platform,
ignore_disk_space,
})
Expand Down Expand Up @@ -369,16 +391,24 @@ fn get_command() -> Command {
Arg::new(OPTION_CUSTOM_NAME)
.short('m')
.long(OPTION_CUSTOM_NAME)
.help("Specify an optional custom name for the database owner's messages in exports\n")
.help(format!("Specify an optional custom name for the database owner's messages in exports\nConflicts with --{OPTION_USE_CALLER_ID}\n"))
.display_order(10)
)
.arg(
Arg::new(OPTION_USE_CALLER_ID)
.short('i')
.long(OPTION_USE_CALLER_ID)
.help(format!("Use the database owner's caller ID in exports instead of \"Me\"\nConflicts with --{OPTION_CUSTOM_NAME}\n"))
.action(ArgAction::SetTrue)
.display_order(11)
)
.arg(
Arg::new(OPTION_BYPASS_FREE_SPACE_CHECK)
.short('b')
.long(OPTION_BYPASS_FREE_SPACE_CHECK)
.help("Bypass the disk space check when exporting data\nBy default, exports will not run if there is not enough free disk space\n")
.action(ArgAction::SetTrue)
.display_order(11)
.display_order(12)
)
}

Expand Down Expand Up @@ -420,6 +450,7 @@ mod arg_tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
use_caller_id: false,
platform: Platform::default(),
ignore_disk_space: false,
};
Expand Down Expand Up @@ -492,6 +523,19 @@ mod arg_tests {
assert!(actual.is_err());
}

#[test]
fn cant_build_option_diagnostic_flag_with_caller_id() {
// Get matches from sample args
let cli_args: Vec<&str> = vec!["imessage-exporter", "-d", "-i"];
let command = get_command();
let args = command.get_matches_from(cli_args);

// Build the Options
let actual = Options::from_args(&args);

assert!(actual.is_err());
}

#[test]
fn can_build_option_export_html() {
// Get matches from sample args
Expand All @@ -514,6 +558,7 @@ mod arg_tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
use_caller_id: false,
platform: Platform::default(),
ignore_disk_space: false,
};
Expand Down Expand Up @@ -542,6 +587,7 @@ mod arg_tests {
query_context: QueryContext::default(),
no_lazy: true,
custom_name: None,
use_caller_id: false,
platform: Platform::default(),
ignore_disk_space: false,
};
Expand Down Expand Up @@ -639,6 +685,90 @@ mod arg_tests {

assert!(actual.is_err());
}

#[test]
fn can_build_option_custom_name() {
// Get matches from sample args
let cli_args: Vec<&str> = vec!["imessage-exporter", "-f", "txt", "-m", "Name"];
let command = get_command();
let args = command.get_matches_from(cli_args);

// Build the Options
let actual = Options::from_args(&args).unwrap();

// Expected data
let expected = Options {
db_path: default_db_path(),
attachment_root: None,
attachment_manager: AttachmentManager::default(),
diagnostic: false,
export_type: Some(ExportType::Txt),
export_path: validate_path(None, &None).unwrap(),
query_context: QueryContext::default(),
no_lazy: false,
custom_name: Some("Name".to_string()),
use_caller_id: false,
platform: Platform::default(),
ignore_disk_space: false,
};

assert_eq!(actual, expected);
}

#[test]
fn can_build_option_caller_id() {
// Get matches from sample args
let cli_args: Vec<&str> = vec!["imessage-exporter", "-f", "txt", "-i"];
let command = get_command();
let args = command.get_matches_from(cli_args);

// Build the Options
let actual = Options::from_args(&args).unwrap();

// Expected data
let expected = Options {
db_path: default_db_path(),
attachment_root: None,
attachment_manager: AttachmentManager::default(),
diagnostic: false,
export_type: Some(ExportType::Txt),
export_path: validate_path(None, &None).unwrap(),
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
use_caller_id: true,
platform: Platform::default(),
ignore_disk_space: false,
};

assert_eq!(actual, expected);
}

#[test]
fn cant_build_option_custom_name_and_caller_id() {
// Get matches from sample args
let cli_args: Vec<&str> = vec!["imessage-exporter", "-f", "txt", "-m", "Name", "-i"];
let command = get_command();
let args = command.get_matches_from(cli_args);

// Build the Options
let actual = Options::from_args(&args);

assert!(actual.is_err());
}

#[test]
fn cant_build_option_caller_id_no_export() {
// Get matches from sample args
let cli_args: Vec<&str> = vec!["imessage-exporter", "-f", "txt", "-m", "Name", "-i"];
let command = get_command();
let args = command.get_matches_from(cli_args);

// Build the Options
let actual = Options::from_args(&args);

assert!(actual.is_err());
}
}

#[cfg(test)]
Expand Down

0 comments on commit 88ff7cd

Please sign in to comment.