From 7e2800d89aaabcb4b2ffa1fda7fe35f4bea65a85 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:02:29 +0800 Subject: [PATCH 1/7] Make necessary changes for adopting the latest iphone_backup_decrypt --- Whatsapp_Chat_Exporter/__main__.py | 9 +--- Whatsapp_Chat_Exporter/ios_media_handler.py | 49 +++++++++------------ 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/Whatsapp_Chat_Exporter/__main__.py b/Whatsapp_Chat_Exporter/__main__.py index a332181..fd6e2b6 100644 --- a/Whatsapp_Chat_Exporter/__main__.py +++ b/Whatsapp_Chat_Exporter/__main__.py @@ -188,13 +188,6 @@ def main(): action='store_true', help="Use Whatsapp Business default files (iOS only)" ) - parser.add_argument( - "--preserve-timestamp", - dest="preserve_timestamp", - default=False, - action='store_true', - help="Preserve the modification timestamp of the extracted files (iOS only)" - ) parser.add_argument( "--wab", "--wa-backup", @@ -403,7 +396,7 @@ def main(): args.media = identifiers.DOMAIN if args.backup is not None: if not os.path.isdir(args.media): - ios_media_handler.extract_media(args.backup, identifiers, args.preserve_timestamp) + ios_media_handler.extract_media(args.backup, identifiers) else: print("WhatsApp directory already exists, skipping WhatsApp file extraction.") if args.db is None: diff --git a/Whatsapp_Chat_Exporter/ios_media_handler.py b/Whatsapp_Chat_Exporter/ios_media_handler.py index 8fa0f65..6ece83d 100644 --- a/Whatsapp_Chat_Exporter/ios_media_handler.py +++ b/Whatsapp_Chat_Exporter/ios_media_handler.py @@ -3,55 +3,49 @@ import shutil import sqlite3 import os -import time import getpass -import threading from Whatsapp_Chat_Exporter.utility import WhatsAppIdentifier try: from iphone_backup_decrypt import EncryptedBackup, RelativePath - from iphone_backup_decrypt import FailedToDecryptError except ModuleNotFoundError: support_encrypted = False else: support_encrypted = True -def extract_encrypted(base_dir, password, identifiers, bplist_reader=None): +def extract_encrypted(base_dir, password, identifiers): + print("Trying to decrypt the iOS backup...", end="") backup = EncryptedBackup(backup_directory=base_dir, passphrase=password, cleanup=False, check_same_thread=False) - print("Decrypting WhatsApp database...", end="") + print("Done\nDecrypting WhatsApp database...", end="") try: backup.extract_file( relative_path=RelativePath.WHATSAPP_MESSAGES, - domain=identifiers.DOMAIN, + domain_like=identifiers.DOMAIN, output_filename=identifiers.MESSAGE ) backup.extract_file( relative_path=RelativePath.WHATSAPP_CONTACTS, - domain=identifiers.DOMAIN, + domain_like=identifiers.DOMAIN, output_filename=identifiers.CONTACT ) - except FailedToDecryptError: + except ValueError: print("Failed to decrypt backup: incorrect password?") exit() else: print("Done") - extract_thread = threading.Thread( - target=backup.extract_files_by_domain, - args=(identifiers.DOMAIN, identifiers.DOMAIN, bplist_reader) + + def extract_progress_handler(file_id, domain, relative_path, n, total_files): + if n % 100 == 0: + print(f"Decrypting and extracting files...({n}/{total_files})", end="\r") + return True + + backup.extract_files( + domain_like=identifiers.DOMAIN, + output_folder=identifiers.DOMAIN, + preserve_folders=True, + filter_callback=extract_progress_handler ) - extract_thread.daemon = True - extract_thread.start() - dot = 0 - while extract_thread.is_alive(): - print(f"Decrypting and extracting files{'.' * dot}{' ' * (3 - dot)}", end="\r") - if dot < 3: - dot += 1 - time.sleep(0.5) - else: - dot = 0 - time.sleep(0.4) - print(f"All required files decrypted and extracted.", end="\n") - extract_thread.handled = True + print(f"All required files are decrypted and extracted. ", end="\n") return backup @@ -70,10 +64,7 @@ def is_encrypted(base_dir): return False -def extract_media(base_dir, identifiers, preserve_timestamp=False): - if preserve_timestamp: - from Whatsapp_Chat_Exporter.bplist import BPListReader - preserve_timestamp = BPListReader +def extract_media(base_dir, identifiers): if is_encrypted(base_dir): if not support_encrypted: print("You don't have the dependencies to handle encrypted backup.") @@ -82,7 +73,7 @@ def extract_media(base_dir, identifiers, preserve_timestamp=False): return False print("Encryption detected on the backup!") password = getpass.getpass("Enter the password for the backup:") - extract_encrypted(base_dir, password, identifiers, preserve_timestamp) + extract_encrypted(base_dir, password, identifiers) else: wts_db = os.path.join(base_dir, identifiers.MESSAGE[:2], identifiers.MESSAGE) contact_db = os.path.join(base_dir, identifiers.CONTACT[:2], identifiers.CONTACT) From b01d81ddece217048dd1e6e556bb59b6c0100fd9 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:20:57 +0800 Subject: [PATCH 2/7] Lower the chunk size for decryption --- Whatsapp_Chat_Exporter/__main__.py | 9 ++++++++- Whatsapp_Chat_Exporter/ios_media_handler.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Whatsapp_Chat_Exporter/__main__.py b/Whatsapp_Chat_Exporter/__main__.py index fd6e2b6..ae953b5 100644 --- a/Whatsapp_Chat_Exporter/__main__.py +++ b/Whatsapp_Chat_Exporter/__main__.py @@ -246,6 +246,13 @@ def main(): action='store_true', help="Create a copy of the media seperated per chat in /separated/ directory" ) + parser.add_argument( + "--decrypt-chunk-size", + dest="decrypt_chunk_size", + default=1 * 1024 * 1024, + type=int, + help="Specify the chunk size for decrypting iOS backup, which may affect the decryption speed." + ) args = parser.parse_args() # Check for updates @@ -396,7 +403,7 @@ def main(): args.media = identifiers.DOMAIN if args.backup is not None: if not os.path.isdir(args.media): - ios_media_handler.extract_media(args.backup, identifiers) + ios_media_handler.extract_media(args.backup, identifiers, args.decrypt_chunk_size) else: print("WhatsApp directory already exists, skipping WhatsApp file extraction.") if args.db is None: diff --git a/Whatsapp_Chat_Exporter/ios_media_handler.py b/Whatsapp_Chat_Exporter/ios_media_handler.py index 6ece83d..4e0dad2 100644 --- a/Whatsapp_Chat_Exporter/ios_media_handler.py +++ b/Whatsapp_Chat_Exporter/ios_media_handler.py @@ -13,9 +13,15 @@ else: support_encrypted = True -def extract_encrypted(base_dir, password, identifiers): +def extract_encrypted(base_dir, password, identifiers, decrypt_chunk_size): print("Trying to decrypt the iOS backup...", end="") - backup = EncryptedBackup(backup_directory=base_dir, passphrase=password, cleanup=False, check_same_thread=False) + backup = EncryptedBackup( + backup_directory=base_dir, + passphrase=password, + cleanup=False, + check_same_thread=False, + decrypt_chunk_size=decrypt_chunk_size + ) print("Done\nDecrypting WhatsApp database...", end="") try: backup.extract_file( @@ -64,7 +70,7 @@ def is_encrypted(base_dir): return False -def extract_media(base_dir, identifiers): +def extract_media(base_dir, identifiers, decrypt_chunk_size): if is_encrypted(base_dir): if not support_encrypted: print("You don't have the dependencies to handle encrypted backup.") @@ -73,7 +79,7 @@ def extract_media(base_dir, identifiers): return False print("Encryption detected on the backup!") password = getpass.getpass("Enter the password for the backup:") - extract_encrypted(base_dir, password, identifiers) + extract_encrypted(base_dir, password, identifiers, decrypt_chunk_size) else: wts_db = os.path.join(base_dir, identifiers.MESSAGE[:2], identifiers.MESSAGE) contact_db = os.path.join(base_dir, identifiers.CONTACT[:2], identifiers.CONTACT) From c69d053049cd916cd26a34a946280e82974e3247 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:18:26 +0800 Subject: [PATCH 3/7] Import back the BPListReader --- Whatsapp_Chat_Exporter/ios_media_handler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Whatsapp_Chat_Exporter/ios_media_handler.py b/Whatsapp_Chat_Exporter/ios_media_handler.py index 4e0dad2..f15df14 100644 --- a/Whatsapp_Chat_Exporter/ios_media_handler.py +++ b/Whatsapp_Chat_Exporter/ios_media_handler.py @@ -5,6 +5,7 @@ import sqlite3 import os import getpass from Whatsapp_Chat_Exporter.utility import WhatsAppIdentifier +from bplist import BPListReader try: from iphone_backup_decrypt import EncryptedBackup, RelativePath except ModuleNotFoundError: @@ -133,11 +134,10 @@ def extract_media(base_dir, identifiers, decrypt_chunk_size): pass elif flags == 1: shutil.copyfile(os.path.join(base_dir, folder, hashes), destination) - if preserve_timestamp: - metadata = BPListReader(row["metadata"]).parse() - creation = metadata["$objects"][1]["Birth"] - modification = metadata["$objects"][1]["LastModified"] - os.utime(destination, (modification, modification)) + metadata = BPListReader(row["metadata"]).parse() + creation = metadata["$objects"][1]["Birth"] + modification = metadata["$objects"][1]["LastModified"] + os.utime(destination, (modification, modification)) if row["_index"] % 100 == 0: print(f"Extracting WhatsApp files...({row['_index']}/{total_row_number})", end="\r") row = c.fetchone() From 6370b81299b85094b5f2e105c80048255980a7c2 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:55:42 +0800 Subject: [PATCH 4/7] Update import --- Whatsapp_Chat_Exporter/ios_media_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Whatsapp_Chat_Exporter/ios_media_handler.py b/Whatsapp_Chat_Exporter/ios_media_handler.py index f15df14..0342325 100644 --- a/Whatsapp_Chat_Exporter/ios_media_handler.py +++ b/Whatsapp_Chat_Exporter/ios_media_handler.py @@ -5,7 +5,7 @@ import sqlite3 import os import getpass from Whatsapp_Chat_Exporter.utility import WhatsAppIdentifier -from bplist import BPListReader +from Whatsapp_Chat_Exporter.bplist import BPListReader try: from iphone_backup_decrypt import EncryptedBackup, RelativePath except ModuleNotFoundError: From 5449646a1b8af529e324b6df6e2e6bd356089c8e Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:57:46 +0800 Subject: [PATCH 5/7] Add lazy loading to image (#103) --- Whatsapp_Chat_Exporter/whatsapp.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Whatsapp_Chat_Exporter/whatsapp.html b/Whatsapp_Chat_Exporter/whatsapp.html index 72c9b6b..4688236 100644 --- a/Whatsapp_Chat_Exporter/whatsapp.html +++ b/Whatsapp_Chat_Exporter/whatsapp.html @@ -143,7 +143,7 @@ {% else %} {% if "image/" in msg.mime %} - + {% elif "audio/" in msg.mime %}