mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-09-24 11:18:49 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fa0a613b0 |
@@ -113,7 +113,7 @@ Do an iPhone/iPad Backup with iTunes/Finder first.
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> If you are working on unencrypted iOS/iPadOS backup, skip this.
|
> If you are working on unencrypted iOS/iPadOS backup, skip this.
|
||||||
|
|
||||||
If you want to work on an encrypted iOS/iPadOS Backup, you should install `iphone_backup_decrypt` from [KnugiHK/iphone_backup_decrypt](https://github.com/KnugiHK/iphone_backup_decrypt) before you run the extract_iphone_media.py.
|
If you want to work on an encrypted iOS/iPadOS Backup, you should install iphone_backup_decrypt from [KnugiHK/iphone_backup_decrypt](https://github.com/KnugiHK/iphone_backup_decrypt) before you run the extract_iphone_media.py.
|
||||||
```sh
|
```sh
|
||||||
pip install git+https://github.com/KnugiHK/iphone_backup_decrypt
|
pip install git+https://github.com/KnugiHK/iphone_backup_decrypt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -932,14 +932,26 @@ def calls(db, data, timezone_offset, filter_chat):
|
|||||||
c = db.cursor()
|
c = db.cursor()
|
||||||
|
|
||||||
# Check if there are any calls that match the filter
|
# Check if there are any calls that match the filter
|
||||||
total_row_number = _get_calls_count(c, filter_chat)
|
# The order matters here, modern query should be attempted first,
|
||||||
|
# if it fails, we can be pretty sure that legacy one will work,
|
||||||
|
# but not the other way around. This is because legacy query is
|
||||||
|
# more simple and less likely to have issues with missing tables/columns.
|
||||||
|
try:
|
||||||
|
total_row_number = _get_calls_count_modern(c, filter_chat)
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
total_row_number = _get_calls_count_legacy(c, filter_chat)
|
||||||
if total_row_number == 0:
|
if total_row_number == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
logging.info(f"Processing calls...({total_row_number})", extra={"clear": True})
|
logging.info(f"Processing calls...({total_row_number})", extra={"clear": True})
|
||||||
|
|
||||||
# Fetch call data
|
# Fetch call data
|
||||||
calls_data = _fetch_calls_data(c, filter_chat)
|
# Again, we try modern query first and fallback to legacy if it fails,
|
||||||
|
# for the same reasons as above.
|
||||||
|
try:
|
||||||
|
calls_data = _fetch_calls_data_modern(c, filter_chat)
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
calls_data = _fetch_calls_data_legacy(c, filter_chat)
|
||||||
|
|
||||||
# Create a chat store for all calls
|
# Create a chat store for all calls
|
||||||
chat = ChatStore(Device.ANDROID, "WhatsApp Calls")
|
chat = ChatStore(Device.ANDROID, "WhatsApp Calls")
|
||||||
@@ -955,7 +967,29 @@ def calls(db, data, timezone_offset, filter_chat):
|
|||||||
data.add_chat("000000000000000", chat)
|
data.add_chat("000000000000000", chat)
|
||||||
logging.info(f"Processed {total_row_number} calls in {convert_time_unit(total_time)}")
|
logging.info(f"Processed {total_row_number} calls in {convert_time_unit(total_time)}")
|
||||||
|
|
||||||
def _get_calls_count(c, filter_chat):
|
|
||||||
|
def _get_calls_count_legacy(c, filter_chat):
|
||||||
|
"""Get the count of call records that match the filter."""
|
||||||
|
|
||||||
|
# Build the filter conditions
|
||||||
|
include_filter = get_chat_condition(filter_chat[0], True, ["key_remote_jid"])
|
||||||
|
exclude_filter = get_chat_condition(filter_chat[1], False, ["key_remote_jid"])
|
||||||
|
|
||||||
|
query = f"""SELECT count(),
|
||||||
|
jid.raw_string as key_remote_jid
|
||||||
|
FROM call_log
|
||||||
|
INNER JOIN jid
|
||||||
|
ON call_log.jid_row_id = jid._id
|
||||||
|
LEFT JOIN chat
|
||||||
|
ON call_log.jid_row_id = chat.jid_row_id
|
||||||
|
WHERE 1=1
|
||||||
|
{include_filter}
|
||||||
|
{exclude_filter}"""
|
||||||
|
c.execute(query)
|
||||||
|
return c.fetchone()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_calls_count_modern(c, filter_chat):
|
||||||
"""Get the count of call records that match the filter."""
|
"""Get the count of call records that match the filter."""
|
||||||
|
|
||||||
# Build the filter conditions
|
# Build the filter conditions
|
||||||
@@ -980,7 +1014,36 @@ def _get_calls_count(c, filter_chat):
|
|||||||
return c.fetchone()[0]
|
return c.fetchone()[0]
|
||||||
|
|
||||||
|
|
||||||
def _fetch_calls_data(c, filter_chat):
|
def _fetch_calls_data_legacy(c, filter_chat):
|
||||||
|
"""Fetch call data from the database."""
|
||||||
|
|
||||||
|
# Build the filter conditions
|
||||||
|
include_filter = get_chat_condition(filter_chat[0], True, ["key_remote_jid"])
|
||||||
|
exclude_filter = get_chat_condition(filter_chat[1], False, ["key_remote_jid"])
|
||||||
|
|
||||||
|
query = f"""SELECT call_log._id,
|
||||||
|
jid.raw_string as key_remote_jid,
|
||||||
|
from_me,
|
||||||
|
call_id,
|
||||||
|
timestamp,
|
||||||
|
video_call,
|
||||||
|
duration,
|
||||||
|
call_result,
|
||||||
|
bytes_transferred,
|
||||||
|
chat.subject as chat_subject
|
||||||
|
FROM call_log
|
||||||
|
INNER JOIN jid
|
||||||
|
ON call_log.jid_row_id = jid._id
|
||||||
|
LEFT JOIN chat
|
||||||
|
ON call_log.jid_row_id = chat.jid_row_id
|
||||||
|
WHERE 1=1
|
||||||
|
{include_filter}
|
||||||
|
{exclude_filter}"""
|
||||||
|
c.execute(query)
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_calls_data_modern(c, filter_chat):
|
||||||
"""Fetch call data from the database."""
|
"""Fetch call data from the database."""
|
||||||
|
|
||||||
# Build the filter conditions
|
# Build the filter conditions
|
||||||
|
|||||||
@@ -213,9 +213,6 @@ def rendering(
|
|||||||
if "??" not in headline:
|
if "??" not in headline:
|
||||||
raise ValueError("Headline must contain '??' to replace with name")
|
raise ValueError("Headline must contain '??' to replace with name")
|
||||||
headline = headline.replace("??", name)
|
headline = headline.replace("??", name)
|
||||||
# Create a temporary lookup map only at render-time;
|
|
||||||
# media preview in reply is a UI-specific concern and
|
|
||||||
# is ignored by the core database processing
|
|
||||||
with open(output_file_name, "w", encoding="utf-8") as f:
|
with open(output_file_name, "w", encoding="utf-8") as f:
|
||||||
f.write(
|
f.write(
|
||||||
template.render(
|
template.render(
|
||||||
@@ -229,8 +226,7 @@ def rendering(
|
|||||||
previous=previous,
|
previous=previous,
|
||||||
status=chat.status,
|
status=chat.status,
|
||||||
media_base=chat.media_base,
|
media_base=chat.media_base,
|
||||||
headline=headline,
|
headline=headline
|
||||||
msg_map={m.key_id: m for m in msgs}.get
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% set replied_msg = msg_map(msg.reply) %}
|
{% set replied_msg = msgs | selectattr('key_id', 'equalto', msg.reply) | first %}
|
||||||
{% if replied_msg and replied_msg.media == true %}
|
{% if replied_msg and replied_msg.media == true %}
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
{% if "image/" in replied_msg.mime %}
|
{% if "image/" in replied_msg.mime %}
|
||||||
@@ -461,7 +461,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% set replied_msg = msg_map(msg.reply) %}
|
{% set replied_msg = msgs | selectattr('key_id', 'equalto', msg.reply) | first %}
|
||||||
{% if replied_msg and replied_msg.media == true %}
|
{% if replied_msg and replied_msg.media == true %}
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
{% if "image/" in replied_msg.mime %}
|
{% if "image/" in replied_msg.mime %}
|
||||||
|
|||||||
Reference in New Issue
Block a user