From 87c1555f030fa20dae931c421b4652bd02bb3f03 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:02:52 +0800 Subject: [PATCH 1/7] Add windows 11 arm and macos x64 to binary compiling --- .github/workflows/compile-binary.yml | 84 ++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/.github/workflows/compile-binary.yml b/.github/workflows/compile-binary.yml index 013bb34..e47d44a 100644 --- a/.github/workflows/compile-binary.yml +++ b/.github/workflows/compile-binary.yml @@ -10,7 +10,6 @@ permissions: id-token: write attestations: write - jobs: linux: runs-on: ubuntu-latest @@ -37,11 +36,10 @@ jobs: subject-path: ./wtsexporter_linux_x64 - uses: actions/upload-artifact@v6 with: - name: binary-linux - path: | - ./wtsexporter_linux_x64 + name: binary-linux-x64 + path: ./wtsexporter_linux_x64 - windows: + windows-x64: runs-on: windows-latest steps: - uses: actions/checkout@v6 @@ -57,19 +55,45 @@ jobs: - name: Build binary with Nuitka run: | python -m nuitka --onefile --include-data-file=./Whatsapp_Chat_Exporter/whatsapp.html=./Whatsapp_Chat_Exporter/whatsapp.html --assume-yes-for-downloads Whatsapp_Chat_Exporter --output-filename=wtsexporter - Rename-Item -Path "wtsexporter.exe" -NewName "wtsexporter_x64.exe" - Get-FileHash wtsexporter_x64.exe + Rename-Item -Path "wtsexporter.exe" -NewName "wtsexporter_win_x64.exe" + Get-FileHash wtsexporter_win_x64.exe - name: Generate artifact attestation uses: actions/attest-build-provenance@v3 with: - subject-path: .\wtsexporter_x64.exe + subject-path: .\wtsexporter_win_x64.exe - uses: actions/upload-artifact@v6 with: - name: binary-windows - path: | - .\wtsexporter_x64.exe + name: binary-windows-x64 + path: .\wtsexporter_win_x64.exe - macos: + windows-arm: + runs-on: windows-11-arm + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pycryptodome javaobj-py3 ordered-set zstandard nuitka==2.8.9 + pip install . + - name: Build binary with Nuitka + run: | + python -m nuitka --onefile --include-data-file=./Whatsapp_Chat_Exporter/whatsapp.html=./Whatsapp_Chat_Exporter/whatsapp.html --assume-yes-for-downloads Whatsapp_Chat_Exporter --output-filename=wtsexporter + Rename-Item -Path "wtsexporter.exe" -NewName "wtsexporter_win_arm64.exe" + Get-FileHash wtsexporter_win_arm64.exe + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: .\wtsexporter_win_arm64.exe + - uses: actions/upload-artifact@v6 + with: + name: binary-windows-arm64 + path: .\wtsexporter_win_arm64.exe + + macos-arm: runs-on: macos-latest steps: - uses: actions/checkout@v6 @@ -86,7 +110,8 @@ jobs: run: | python -m nuitka --onefile \ --include-data-file=./Whatsapp_Chat_Exporter/whatsapp.html=./Whatsapp_Chat_Exporter/whatsapp.html \ - --assume-yes-for-downloads Whatsapp_Chat_Exporter --output-filename=wtsexporter_macos_arm64 + --assume-yes-for-downloads Whatsapp_Chat_Exporter --output-filename=wtsexporter + mv wtsexporter wtsexporter_macos_arm64 shasum -a 256 wtsexporter_macos_arm64 - name: Generate artifact attestation uses: actions/attest-build-provenance@v3 @@ -94,7 +119,34 @@ jobs: subject-path: ./wtsexporter_macos_arm64 - uses: actions/upload-artifact@v6 with: - name: binary-macos - path: | - ./wtsexporter_macos_arm64 + name: binary-macos-arm64 + path: ./wtsexporter_macos_arm64 + macos-intel: + runs-on: macos-15-intel + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pycryptodome javaobj-py3 ordered-set zstandard nuitka==2.8.9 + pip install . + - name: Build binary with Nuitka + run: | + python -m nuitka --onefile \ + --include-data-file=./Whatsapp_Chat_Exporter/whatsapp.html=./Whatsapp_Chat_Exporter/whatsapp.html \ + --assume-yes-for-downloads Whatsapp_Chat_Exporter --output-filename=wtsexporter + mv wtsexporter wtsexporter_macos_x64 + shasum -a 256 wtsexporter_macos_x64 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: ./wtsexporter_macos_x64 + - uses: actions/upload-artifact@v6 + with: + name: binary-macos-x64 + path: ./wtsexporter_macos_x64 \ No newline at end of file From 929534ff8091d94f967e2429ffd884af9ecd2ab3 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:17:00 +0800 Subject: [PATCH 2/7] Add windows 11 arm and macos 15 intel to CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cfa745..26ddfed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, windows-latest, macos-latest, windows-11-arm, macos-15-intel] python-version: ["3.13", "3.14"] include: - os: ubuntu-latest From 0a14da91089105f870858f9d26f17cfeae202f3a Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:31:47 +0800 Subject: [PATCH 3/7] Reduce CI platforms --- .github/workflows/ci.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26ddfed..d8a4cd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,15 +13,19 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, windows-11-arm, macos-15-intel] - python-version: ["3.13", "3.14"] + os: [ubuntu-latest] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] include: - - os: ubuntu-latest - python-version: "3.10" - - os: ubuntu-latest - python-version: "3.11" - - os: ubuntu-latest - python-version: "3.12" + - os: windows-latest + python-version: "3.13" + - os: macos-latest + python-version: "3.13" + - os: windows-11-arm + python-version: "3.13" + - os: macos-15-intel + python-version: "3.13" + - os: windows-latest + python-version: "3.14" steps: - name: Checkout code From 8f29fa05057d30cd3eb32979aa86d26e21087e9c Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:35:02 +0800 Subject: [PATCH 4/7] Center the version string in the exporter banner --- Whatsapp_Chat_Exporter/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Whatsapp_Chat_Exporter/__main__.py b/Whatsapp_Chat_Exporter/__main__.py index b844921..07a341b 100644 --- a/Whatsapp_Chat_Exporter/__main__.py +++ b/Whatsapp_Chat_Exporter/__main__.py @@ -42,7 +42,7 @@ WTSEXPORTER_BANNER = f"""======================================================= ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ WhatsApp Chat Exporter: A customizable Android and iOS/iPadOS WhatsApp database parser - Version: {__version__} +{f"Version: {__version__}".center(104)} ========================================================================================================""" From 5a20953a81c1e58e45ad2a2472f4306bf8c4db00 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:51:29 +0800 Subject: [PATCH 5/7] Optimize quoted message lookups via global in-memory mapping This change replaces the inefficient N+1 SQL query pattern with a pre-computed hash map. By fetching `ZSTANZAID` and `ZTEXT` pairs globally before processing, the exporter can resolve quoted message content in O(1) time. Crucially, this maintains parity with the Android exporter by ensuring that replies to messages outside the current date or chat filters are still correctly rendered, providing full conversational context without the performance penalty of repeated database hits. --- Whatsapp_Chat_Exporter/ios_handler.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Whatsapp_Chat_Exporter/ios_handler.py b/Whatsapp_Chat_Exporter/ios_handler.py index 0501ac5..243ae58 100644 --- a/Whatsapp_Chat_Exporter/ios_handler.py +++ b/Whatsapp_Chat_Exporter/ios_handler.py @@ -178,6 +178,10 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat, """ c.execute(messages_query) + reply_query = """SELECT ZSTANZAID, ZTEXT FROM ZWAMESSAGE WHERE ZTEXT IS NOT NULL""" + cursor2.execute(reply_query) + message_map = {row[0][:17]: row[1] for row in cursor2.fetchall() if row[0]} + # Process each message i = 0 content = c.fetchone() @@ -207,7 +211,7 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat, ) # Process message data - invalid = process_message_data(message, content, is_group_message, data, cursor2, no_reply) + invalid = process_message_data(message, content, is_group_message, data, message_map, no_reply) # Add valid messages to chat if not invalid: @@ -221,7 +225,7 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat, logger.info(f"Processed {total_row_number} messages{CLEAR_LINE}") -def process_message_data(message, content, is_group_message, data, cursor2, no_reply): +def process_message_data(message, content, is_group_message, data, message_map, no_reply): """Process and set message data from content row.""" # Handle group sender info if is_group_message and content["ZISFROMME"] == 0: @@ -247,14 +251,7 @@ def process_message_data(message, content, is_group_message, data, cursor2, no_r if content["ZMETADATA"] is not None and content["ZMETADATA"].startswith(b"\x2a\x14") and not no_reply: quoted = content["ZMETADATA"][2:19] message.reply = quoted.decode() - cursor2.execute(f"""SELECT ZTEXT - FROM ZWAMESSAGE - WHERE ZSTANZAID LIKE '{message.reply}%'""") - quoted_content = cursor2.fetchone() - if quoted_content and "ZTEXT" in quoted_content: - message.quoted_data = quoted_content["ZTEXT"] - else: - message.quoted_data = None + message.quoted_data = message_map.get(message.reply) # Handle stickers if content["ZMESSAGETYPE"] == 15: From 3538c81605e33e911c746b416a87627ba9ca9c4c Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:59:51 +0800 Subject: [PATCH 6/7] Enhance qouted message resolution to include media caption Modified the `reply_query` to support messages that may not have body text but contain media caption. --- Whatsapp_Chat_Exporter/ios_handler.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Whatsapp_Chat_Exporter/ios_handler.py b/Whatsapp_Chat_Exporter/ios_handler.py index 243ae58..5a3230e 100644 --- a/Whatsapp_Chat_Exporter/ios_handler.py +++ b/Whatsapp_Chat_Exporter/ios_handler.py @@ -178,9 +178,16 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat, """ c.execute(messages_query) - reply_query = """SELECT ZSTANZAID, ZTEXT FROM ZWAMESSAGE WHERE ZTEXT IS NOT NULL""" + reply_query = """SELECT ZSTANZAID, + ZTEXT, + ZTITLE + FROM ZWAMESSAGE + LEFT JOIN ZWAMEDIAITEM + ON ZWAMESSAGE.Z_PK = ZWAMEDIAITEM.ZMESSAGE + WHERE ZTEXT IS NOT NULL + OR ZTITLE IS NOT NULL;""" cursor2.execute(reply_query) - message_map = {row[0][:17]: row[1] for row in cursor2.fetchall() if row[0]} + message_map = {row[0][:17]: row[1] or row[2] for row in cursor2.fetchall() if row[0]} # Process each message i = 0 From 401abfb7320ee4fbb33af75e58822ce8f6d0f789 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:19:09 +0800 Subject: [PATCH 7/7] Bump version --- README.md | 33 ++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3566b9f..ec2cad4 100644 --- a/README.md +++ b/README.md @@ -145,22 +145,24 @@ After extracting, you will get this: Invoke the wtsexporter with --help option will show you all options available. ```sh > wtsexporter --help -usage: wtsexporter [-h] [-a] [-i] [-e EXPORTED] [-w WA] [-m MEDIA] [-b BACKUP] [-d DB] [-k [KEY]] +usage: wtsexporter [-h] [--debug] [-a] [-i] [-e EXPORTED] [-w WA] [-m MEDIA] [-b BACKUP] [-d DB] [-k [KEY]] [--call-db [CALL_DB_IOS]] [--wab WAB] [-o OUTPUT] [-j [JSON]] [--txt [TEXT_FORMAT]] [--no-html] - [--size [SIZE]] [--avoid-encoding-json] [--pretty-print-json [PRETTY_PRINT_JSON]] [--per-chat] - [--import] [-t TEMPLATE] [--offline OFFLINE] [--no-avatar] [--experimental-new-theme] + [--size [SIZE]] [--no-reply] [--avoid-encoding-json] [--pretty-print-json [PRETTY_PRINT_JSON]] + [--tg] [--per-chat] [--import] [-t TEMPLATE] [--offline OFFLINE] [--no-avatar] [--old-theme] [--headline HEADLINE] [-c] [--create-separated-media] [--time-offset {-12 to 14}] [--date DATE] [--date-format FORMAT] [--include [phone number ...]] [--exclude [phone number ...]] [--dont-filter-empty] [--enrich-from-vcards ENRICH_FROM_VCARDS] - [--default-country-code DEFAULT_COUNTRY_CODE] [-s] [--check-update] [--assume-first-as-me] - [--business] [--decrypt-chunk-size DECRYPT_CHUNK_SIZE] - [--max-bruteforce-worker MAX_BRUTEFORCE_WORKER] + [--default-country-code DEFAULT_COUNTRY_CODE] [--incremental-merge] [--source-dir SOURCE_DIR] + [--target-dir TARGET_DIR] [-s] [--check-update] [--assume-first-as-me] [--business] + [--decrypt-chunk-size DECRYPT_CHUNK_SIZE] [--max-bruteforce-worker MAX_BRUTEFORCE_WORKER] + [--no-banner] A customizable Android and iOS/iPadOS WhatsApp database parser that will give you the history of your WhatsApp conversations in HTML and JSON. Android Backup Crypt12, Crypt14 and Crypt15 supported. options: -h, --help show this help message and exit + --debug Enable debug mode Device Type: -a, --android Define the target as Android @@ -188,12 +190,14 @@ Output Options: --no-html Do not output html files --size, --output-size, --split [SIZE] Maximum (rough) size of a single output file in bytes, 0 for auto + --no-reply Do not process replies (iOS only) (default: handle replies) JSON Options: --avoid-encoding-json Don't encode non-ascii characters in the output JSON files --pretty-print-json [PRETTY_PRINT_JSON] Pretty print the output JSON. + --tg, --telegram Output the JSON in a format compatible with Telegram export (implies json-per-chat) --per-chat Output the JSON file per chat --import Import JSON file and convert to HTML output @@ -202,8 +206,7 @@ HTML Options: Path to custom HTML template --offline OFFLINE Relative path to offline static files --no-avatar Do not render avatar in HTML output - --experimental-new-theme - Use the newly designed WhatsApp-alike theme + --old-theme Use the old Telegram-alike theme --headline HEADLINE The custom headline for the HTML output. Use '??' as a placeholder for the chat name Media Handling: @@ -232,12 +235,11 @@ Contact Enrichment: will be used. 1 is for US, 66 for Thailand etc. Most likely use the number of your own country Incremental Merging: - --incremental-merge Performs an incremental merge of two exports. Requires setting both --source- - dir and --target-dir. The chats (JSON files only) and media from the source - directory will be merged into the target directory. No chat messages or media - will be deleted from the target directory; only new chat messages and media - will be added to it. This enables chat messages and media to be deleted from - the device to free up space, while ensuring they are preserved in the exported + --incremental-merge Performs an incremental merge of two exports. Requires setting both --source-dir and --target- + dir. The chats (JSON files only) and media from the source directory will be merged into the + target directory. No chat messages or media will be deleted from the target directory; only + new chat messages and media will be added to it. This enables chat messages and media to be + deleted from the device to free up space, while ensuring they are preserved in the exported backups. --source-dir SOURCE_DIR Sets the source directory. Used for performing incremental merges. @@ -253,8 +255,9 @@ Miscellaneous: Specify the chunk size for decrypting iOS backup, which may affect the decryption speed. --max-bruteforce-worker MAX_BRUTEFORCE_WORKER Specify the maximum number of worker for bruteforce decryption. + --no-banner Do not show the banner -WhatsApp Chat Exporter: 0.13.0rc1 Licensed with MIT. See https://wts.knugi.dev/docs?dest=osl for all open source +WhatsApp Chat Exporter: 0.13.0rc2 Licensed with MIT. See https://wts.knugi.dev/docs?dest=osl for all open source licenses. ``` diff --git a/pyproject.toml b/pyproject.toml index ee8ec7e..c92baeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "whatsapp-chat-exporter" -version = "0.13.0rc1" +version = "0.13.0rc2" description = "A Whatsapp database parser that provides history of your Whatsapp conversations in HTML and JSON. Android, iOS, iPadOS, Crypt12, Crypt14, Crypt15 supported." readme = "README.md" authors = [