15 Commits

Author SHA1 Message Date
KnugiHK
93a020f68d Merge branch 'dev' 2026-01-06 21:19:22 +08:00
KnugiHK
401abfb732 Bump version 2026-01-06 21:19:09 +08:00
KnugiHK
3538c81605 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.
2026-01-06 20:59:51 +08:00
KnugiHK
5a20953a81 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.
2026-01-06 20:51:29 +08:00
KnugiHK
8f29fa0505 Center the version string in the exporter banner 2026-01-06 20:35:02 +08:00
KnugiHK
0a14da9108 Reduce CI platforms 2026-01-05 00:31:47 +08:00
KnugiHK
929534ff80 Add windows 11 arm and macos 15 intel to CI 2026-01-05 00:17:00 +08:00
KnugiHK
87c1555f03 Add windows 11 arm and macos x64 to binary compiling 2026-01-05 00:02:52 +08:00
Knugi
fd325b6b59 Update generate-website.yml 2026-01-04 05:51:15 +00:00
Knugi
17e927ffd6 Update README.md 2026-01-02 05:35:59 +00:00
Knugi
5b488359c8 Update README.md 2026-01-02 05:32:39 +00:00
Knugi
d2186447c6 Update README.md 2026-01-02 05:30:22 +00:00
Knugi
82abf7d874 Add Verifying Build Integrity section 2026-01-02 04:53:52 +00:00
Knugi
5e676f2663 Merge pull request #187 from KnugiHK/alert-autofix-4
Potential fix for code scanning alert no. 4: Workflow does not contain permissions
2026-01-02 12:39:56 +08:00
Knugi
5da2772112 Potential fix for code scanning alert no. 4: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-01-02 04:39:37 +00:00
7 changed files with 141 additions and 55 deletions

View File

@@ -8,18 +8,24 @@ on:
jobs:
ci:
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
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

View File

@@ -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

View File

@@ -19,12 +19,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: '24'
- name: Install dependencies
run: npm install marked fs-extra marked-alert
@@ -36,7 +36,7 @@ jobs:
- name: Deploy to gh-pages
if: github.ref == 'refs/heads/main' # Ensure deployment only happens from main
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@@ -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,15 +255,37 @@ 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.
```
# Verifying Build Integrity
To ensure that the binaries provided in the releases were built directly from this source code via GitHub Actions and have not been tampered with, GitHub Artifact Attestations is used. You can verify the authenticity of any pre-built binaries using the GitHub CLI.
> [!NOTE]
> Requires version 0.13.0rc1 or newer. Legacy binaries are unsupported.
### Using Bash (Linux/WSL/macOS)
```bash
for file in wtsexporter*; do ; gh attestation verify "$file" -R KnugiHK/WhatsApp-Chat-Exporter; done
```
### Using PowerShell (Windows)
```powershell
gci "wtsexporter*" | % { gh attestation verify $_.FullName -R KnugiHK/WhatsApp-Chat-Exporter }
```
# Python Support Policy
This project officially supports all non-EOL (End-of-Life) versions of Python. Once a Python version reaches EOL, it is dropped in the next release. See [Python's EOL Schedule](https://devguide.python.org/versions/).
# Legal Stuff & Disclaimer
This is a MIT licensed project.

View File

@@ -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)}
========================================================================================================"""

View File

@@ -178,6 +178,17 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
"""
c.execute(messages_query)
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] or row[2] for row in cursor2.fetchall() if row[0]}
# Process each message
i = 0
content = c.fetchone()
@@ -207,7 +218,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 +232,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 +258,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:

View File

@@ -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 = [