diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cfa745..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] - 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 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 diff --git a/README.md b/README.md index a2eff56..04af342 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/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)} ========================================================================================================""" diff --git a/Whatsapp_Chat_Exporter/ios_handler.py b/Whatsapp_Chat_Exporter/ios_handler.py index 0501ac5..5a3230e 100644 --- a/Whatsapp_Chat_Exporter/ios_handler.py +++ b/Whatsapp_Chat_Exporter/ios_handler.py @@ -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: 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 = [