Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions imessage-exporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ The [releases page](https://github.com/ReagentX/imessage-exporter/releases) prov
All conversations with the specified participants are exported, including group conversations
Example: `-t [email protected],5558675309`

-g, --group-filter <group_name>
Filter exported group conversations by their names
To provide multiple group names, use a comma-separated string
Example: `-g "Family Chat,Work Group"`

Note: When both `--conversation-filter` and `--group-filter` are specified, only conversations that match both filters (intersection) will be exported.

-x, --cleartext-password <password>
Optional password for encrypted iOS backups
This is only used when the source is an encrypted iOS backup directory
Expand Down
46 changes: 41 additions & 5 deletions imessage-exporter/src/app/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ 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";
pub const OPTION_CONVERSATION_FILTER: &str = "conversation-filter";
pub const OPTION_GROUP_FILTER: &str = "group-filter";
pub const OPTION_CLEARTEXT_PASSWORD: &str = "cleartext-password";
pub const OPTION_CUSTOM_CONTACTS_DB_PATH: &str = "contacts-path";

Expand Down Expand Up @@ -82,6 +83,8 @@ pub struct Options {
pub ignore_disk_space: bool,
/// An optional filter for conversation participants
pub conversation_filter: Option<String>,
/// An optional filter for group chat display names
pub group_filter: Option<String>,
/// An optional password for encrypted backups
pub cleartext_password: Option<String>,
/// An optional path to a custom contacts database
Expand All @@ -105,6 +108,7 @@ impl Options {
let platform_type: Option<&String> = args.get_one(OPTION_PLATFORM);
let ignore_disk_space = args.get_flag(OPTION_BYPASS_FREE_SPACE_CHECK);
let conversation_filter: Option<&String> = args.get_one(OPTION_CONVERSATION_FILTER);
let group_filter: Option<&String> = args.get_one(OPTION_GROUP_FILTER);
let cleartext_password: Option<&String> = args.get_one(OPTION_CLEARTEXT_PASSWORD);
let contacts_path: Option<&String> = args.get_one(OPTION_CUSTOM_CONTACTS_DB_PATH);

Expand All @@ -126,9 +130,10 @@ impl Options {
(no_lazy, OPTION_DISABLE_LAZY_LOADING),
(start_date.is_some(), OPTION_START_DATE),
(end_date.is_some(), OPTION_END_DATE),
(custom_name.is_some(), OPTION_CUSTOM_NAME),
(use_caller_id, OPTION_USE_CALLER_ID),
(conversation_filter.is_some(), OPTION_CONVERSATION_FILTER),
(custom_name.is_some(), OPTION_CUSTOM_NAME),
(use_caller_id, OPTION_USE_CALLER_ID),
(conversation_filter.is_some(), OPTION_CONVERSATION_FILTER),
(group_filter.is_some(), OPTION_GROUP_FILTER),
];
for (set, opt) in format_deps {
if set {
Expand All @@ -150,6 +155,7 @@ impl Options {
(use_caller_id, OPTION_USE_CALLER_ID),
(custom_name.is_some(), OPTION_CUSTOM_NAME),
(conversation_filter.is_some(), OPTION_CONVERSATION_FILTER),
(group_filter.is_some(), OPTION_GROUP_FILTER),
];
for (set, opt) in diag_conflicts {
if diagnostic && set {
Expand Down Expand Up @@ -265,6 +271,7 @@ impl Options {
platform,
ignore_disk_space,
conversation_filter: conversation_filter.cloned(),
group_filter: group_filter.cloned(),
cleartext_password: cleartext_password.cloned(),
contacts_path: contacts_path.cloned().map(PathBuf::from),
})
Expand Down Expand Up @@ -447,20 +454,28 @@ fn get_command() -> Command {
.display_order(13)
.value_name("filter"),
)
.arg(
Arg::new(OPTION_GROUP_FILTER)
.short('g')
.long(OPTION_GROUP_FILTER)
.help("Filter exported group conversations by their names\nTo provide multiple group names, use a comma-separated string\nExample: `-g \"Family Chat,Work Group\"`\n")
.display_order(14)
.value_name("group_name"),
)
.arg(
Arg::new(OPTION_CLEARTEXT_PASSWORD)
.short('x')
.long(OPTION_CLEARTEXT_PASSWORD)
.help("Optional password for encrypted iOS backups\nThis is only used when the source is an encrypted iOS backup directory\n")
.display_order(14)
.display_order(15)
.value_name("password"),
)
.arg(
Arg::new(OPTION_CUSTOM_CONTACTS_DB_PATH)
.short('n')
.long(OPTION_CUSTOM_CONTACTS_DB_PATH)
.help("Optional custom path for a macOS or iOS contacts database file\nThis should be resolved automatically, but can be manually provided\nHandles from the messages table will be mapped to names in the provided database\nGenerally, one of `AddressBook-v22.abcddb` or `AddressBook.sqlitedb`\n")
.display_order(15)
.display_order(16)
.value_name("path"),
)
}
Expand All @@ -486,6 +501,7 @@ impl Options {
platform: Platform::macOS,
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
}
Expand Down Expand Up @@ -535,6 +551,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -618,6 +635,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -652,6 +670,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -732,6 +751,7 @@ mod arg_tests {
platform: Platform::iOS,
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -771,6 +791,7 @@ mod arg_tests {
platform: Platform::iOS,
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: Some("password".to_string()),
contacts_path: None,
};
Expand Down Expand Up @@ -826,6 +847,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -857,6 +879,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -889,13 +912,23 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: Some(String::from("[email protected]")),
group_filter: None,
cleartext_password: None,
contacts_path: None,
};

assert_eq!(actual, expected);
}

#[test]
fn can_build_option_group_filter() {
let command = get_command();
let args = command.get_matches_from(["imessage-exporter", "-g", "Family Chat", "-f", "txt"]);

let actual = Options::from_args(&args).unwrap();
assert_eq!(actual.group_filter, Some(String::from("Family Chat")));
}
Comment on lines +923 to +930
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only validates the group_filter field. Consider testing the complete Options struct (like other tests in this module) to ensure all fields are set correctly when using the group filter option.

Copilot uses AI. Check for mistakes.

#[test]
fn can_build_option_full() {
// Get matches from sample args
Expand All @@ -920,6 +953,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -951,6 +985,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: false,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down Expand Up @@ -1024,6 +1059,7 @@ mod arg_tests {
platform: Platform::default(),
ignore_disk_space: true,
conversation_filter: None,
group_filter: None,
cleartext_password: None,
contacts_path: None,
};
Expand Down
Loading