mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-03-07 06:36:06 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ceaa25dd | ||
|
|
e09f18e2f2 | ||
|
|
23114572bd | ||
|
|
2f04b69f38 | ||
|
|
e7c246822b | ||
|
|
2a215d024f | ||
|
|
f267f53007 | ||
|
|
3a30dfc800 | ||
|
|
580eaddb24 | ||
|
|
77b4b784d3 | ||
|
|
507e88d9c3 | ||
|
|
60e1e7d3eb | ||
|
|
774fb6d781 | ||
|
|
3ef3b02230 | ||
|
|
07cc0f3571 | ||
|
|
a1319eb835 | ||
|
|
8cbb0af43a | ||
|
|
28c4a7b99f | ||
|
|
e4c9d42927 | ||
|
|
c274b6b1c0 | ||
|
|
eec739d7cf | ||
|
|
3d7dca0682 |
@@ -7,7 +7,9 @@ A customizable Android and iPhone Whatsapp database parser that will give you th
|
|||||||
**If you plan to uninstall WhatsApp or delete your WhatsApp account, please make a backup of your WhatsApp database. You may want to use this exporter again on the same database in the future as the exporter develops**
|
**If you plan to uninstall WhatsApp or delete your WhatsApp account, please make a backup of your WhatsApp database. You may want to use this exporter again on the same database in the future as the exporter develops**
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
**If you want to use the old release (< 0.5) of the exporter, please follow the [old usage guide](https://github.com/KnugiHK/Whatsapp-Chat-Exporter/blob/main/old_README.md#usage)**
|
**Usage in README may be removed in the future. Check the usage in [Wiki](https://github.com/KnugiHK/Whatsapp-Chat-Exporter/wiki)**.
|
||||||
|
|
||||||
|
**If you want to use the old release (< 0.5) of the exporter, please follow the [old usage guide](https://github.com/KnugiHK/Whatsapp-Chat-Exporter/wiki/Old-Usage#usage)**.
|
||||||
|
|
||||||
First, install the exporter by:
|
First, install the exporter by:
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.8.0"
|
__version__ = "0.8.5"
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ def main():
|
|||||||
dest="template",
|
dest="template",
|
||||||
default=None,
|
default=None,
|
||||||
help="Path to custom HTML template")
|
help="Path to custom HTML template")
|
||||||
|
parser.add_option(
|
||||||
|
"-e",
|
||||||
|
"--embedded",
|
||||||
|
dest="embedded",
|
||||||
|
default=False,
|
||||||
|
action='store_true',
|
||||||
|
help="Embed media into HTML file")
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
if options.android and options.iphone:
|
if options.android and options.iphone:
|
||||||
@@ -167,7 +174,7 @@ def main():
|
|||||||
messages(db, data)
|
messages(db, data)
|
||||||
media(db, data, options.media)
|
media(db, data, options.media)
|
||||||
vcard(db, data)
|
vcard(db, data)
|
||||||
create_html(data, options.output, options.template)
|
create_html(data, options.output, options.template, options.embedded)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
"The message database does not exist. You may specify the path "
|
"The message database does not exist. You may specify the path "
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import sqlite3
|
|||||||
import json
|
import json
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
import pkgutil
|
|
||||||
import io
|
import io
|
||||||
import hmac
|
import hmac
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -114,7 +112,8 @@ def decrypt_backup(database, key, output, crypt=Crypt.CRYPT14):
|
|||||||
raise ValueError("The crypt15 file must be at least 131 bytes")
|
raise ValueError("The crypt15 file must be at least 131 bytes")
|
||||||
t1 = t2 = None
|
t1 = t2 = None
|
||||||
iv = database[8:24]
|
iv = database[8:24]
|
||||||
db_ciphertext = database[131:]
|
db_offset = database[0] + 2 # Skip protobuf + protobuf size and backup type
|
||||||
|
db_ciphertext = database[db_offset:]
|
||||||
|
|
||||||
if t1 != t2:
|
if t1 != t2:
|
||||||
raise ValueError("The signature of key file and backup file mismatch")
|
raise ValueError("The signature of key file and backup file mismatch")
|
||||||
@@ -215,7 +214,8 @@ def messages(db, data):
|
|||||||
messages.media_caption
|
messages.media_caption
|
||||||
FROM messages
|
FROM messages
|
||||||
LEFT JOIN messages_quotes
|
LEFT JOIN messages_quotes
|
||||||
ON messages.quoted_row_id = messages_quotes._id;""")
|
ON messages.quoted_row_id = messages_quotes._id
|
||||||
|
WHERE messages.key_remote_jid <> '-1';""")
|
||||||
i = 0
|
i = 0
|
||||||
content = c.fetchone()
|
content = c.fetchone()
|
||||||
while content is not None:
|
while content is not None:
|
||||||
@@ -414,12 +414,13 @@ def vcard(db, data):
|
|||||||
if not os.path.isdir(base):
|
if not os.path.isdir(base):
|
||||||
Path(base).mkdir(parents=True, exist_ok=True)
|
Path(base).mkdir(parents=True, exist_ok=True)
|
||||||
for index, row in enumerate(rows):
|
for index, row in enumerate(rows):
|
||||||
file_name = "".join(x for x in row[3] if x.isalnum())
|
media_name = row[3] if row[3] else ""
|
||||||
|
file_name = "".join(x for x in media_name if x.isalnum())
|
||||||
file_path = f"{base}/{file_name}.vcf"
|
file_path = f"{base}/{file_name}.vcf"
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
f.write(row[2])
|
f.write(row[2])
|
||||||
data[row[1]]["messages"][row[0]]["data"] = row[3] + \
|
data[row[1]]["messages"][row[0]]["data"] = media_name + \
|
||||||
"The vCard file cannot be displayed here, " \
|
"The vCard file cannot be displayed here, " \
|
||||||
f"however it should be located at {file_path}"
|
f"however it should be located at {file_path}"
|
||||||
data[row[1]]["messages"][row[0]]["mime"] = "text/x-vcard"
|
data[row[1]]["messages"][row[0]]["mime"] = "text/x-vcard"
|
||||||
@@ -427,7 +428,7 @@ def vcard(db, data):
|
|||||||
print(f"Gathering vCards...({index + 1}/{total_row_number})", end="\r")
|
print(f"Gathering vCards...({index + 1}/{total_row_number})", end="\r")
|
||||||
|
|
||||||
|
|
||||||
def create_html(data, output_folder, template=None):
|
def create_html(data, output_folder, template=None, embedded=False):
|
||||||
if template is None:
|
if template is None:
|
||||||
template_dir = os.path.dirname(__file__)
|
template_dir = os.path.dirname(__file__)
|
||||||
template_file = "whatsapp.html"
|
template_file = "whatsapp.html"
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import sqlite3
|
|||||||
import json
|
import json
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import pkgutil
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from bleach import clean as sanitize
|
from bleach import clean as sanitize
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
@@ -230,7 +228,7 @@ def vcard(db, data):
|
|||||||
print(f"Gathering vCards...({index + 1}/{total_row_number})", end="\r")
|
print(f"Gathering vCards...({index + 1}/{total_row_number})", end="\r")
|
||||||
|
|
||||||
|
|
||||||
def create_html(data, output_folder, template=None):
|
def create_html(data, output_folder, template=None, embedded=False):
|
||||||
if template is None:
|
if template is None:
|
||||||
template_dir = os.path.dirname(__file__)
|
template_dir = os.path.dirname(__file__)
|
||||||
template_file = "whatsapp.html"
|
template_file = "whatsapp.html"
|
||||||
|
|||||||
@@ -115,7 +115,10 @@ def extract_media(base_dir):
|
|||||||
folder = hashes[:2]
|
folder = hashes[:2]
|
||||||
flags = row[2]
|
flags = row[2]
|
||||||
if flags == 2:
|
if flags == 2:
|
||||||
os.mkdir(destination)
|
try:
|
||||||
|
os.mkdir(destination)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
elif flags == 1:
|
elif flags == 1:
|
||||||
shutil.copyfile(f"{base_dir}/{folder}/{hashes}", destination)
|
shutil.copyfile(f"{base_dir}/{folder}/{hashes}", destination)
|
||||||
i += 1
|
i += 1
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</video>
|
</video>
|
||||||
{% elif "/" in msg.mime %}
|
{% elif "/" in msg.mime %}
|
||||||
<div style="text-align: center;" class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar">
|
<div style="text-align: center;" class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar">
|
||||||
<p>The file cannot be displayed here, however it should be located at {{ msg.data }}</p>
|
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
</video>
|
</video>
|
||||||
{% elif "/" in msg.mime %}
|
{% elif "/" in msg.mime %}
|
||||||
<div style="text-align: center;" class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar">
|
<div style="text-align: center;" class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar">
|
||||||
<p>The file cannot be displayed here, however it should be located at {{ msg.data }}</p>
|
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-cayman
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Whatsapp-Chat-Exporter
|
|
||||||
A Whatsapp database parser that will give you the history of your Whatsapp conversations in HTML and JSON
|
|
||||||
**If you plan to uninstall WhatsApp or delete your WhatsApp account, please make a backup of your WhatsApp database. You may want to use this exporter again on the same database in the future as the exporter develops**
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
First, clone this repo, and copy all py and html files to a working directory if you want to do so.
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/KnugiHK/Whatsapp-Chat-Exporter.git
|
|
||||||
```
|
|
||||||
Then, ready your WhatsApp database, place them in the root of working directory.
|
|
||||||
* For Android, it is called msgstore.db. If you want name of your contacts, get the contact database, which is called wa.db.
|
|
||||||
* For iPhone, it is called 7c7fba66680ef796b916b067077cc246adacf01d (YES, a hash).
|
|
||||||
|
|
||||||
Next, ready your media folder, place it in the root of working directory.
|
|
||||||
* For Android, copy the WhatsApp directory from your phone directly.
|
|
||||||
* For iPhone, run the extract_iphone_media.py, and you will get a folder called Message.
|
|
||||||
```
|
|
||||||
python extract_iphone_media.py "C:\Users\[Username]\AppData\Roaming\Apple Computer\MobileSync\Backup\[device id]"
|
|
||||||
```
|
|
||||||
And now, you should have something like this:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Last, run the script regarding the type of phone.
|
|
||||||
```
|
|
||||||
python extract.py & :: Android
|
|
||||||
python extract_iphone.py & :: iPhone
|
|
||||||
```
|
|
||||||
And you will get these:
|
|
||||||
#### Private Message
|
|
||||||

|
|
||||||
|
|
||||||
#### Group Message
|
|
||||||

|
|
||||||
8
setup.py
8
setup.py
@@ -37,13 +37,13 @@ setuptools.setup(
|
|||||||
],
|
],
|
||||||
python_requires='>=3.7',
|
python_requires='>=3.7',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'jinja2',
|
'jinja2',
|
||||||
'bleach'
|
'bleach'
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'android_backup': ["pycryptodome"],
|
'android_backup': ["pycryptodome", "javaobj-py3"],
|
||||||
'crypt12': ["pycryptodome"],
|
|
||||||
'crypt12': ["pycryptodome"],
|
'crypt12': ["pycryptodome"],
|
||||||
|
'crypt14': ["pycryptodome"],
|
||||||
'crypt15': ["pycryptodome", "javaobj-py3"]
|
'crypt15': ["pycryptodome", "javaobj-py3"]
|
||||||
},
|
},
|
||||||
entry_points={
|
entry_points={
|
||||||
|
|||||||
Reference in New Issue
Block a user