From 2466e2542a192518c5ad6b7f4128a521901bc2b2 Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:31:29 +0800 Subject: [PATCH] Add date filter for Android #82 --- Whatsapp_Chat_Exporter/__main__.py | 35 ++++++++++++++++++++++--- Whatsapp_Chat_Exporter/extract.py | 41 +++++++++++++++++++++--------- Whatsapp_Chat_Exporter/filter.html | 12 +++++++++ 3 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 Whatsapp_Chat_Exporter/filter.html diff --git a/Whatsapp_Chat_Exporter/__main__.py b/Whatsapp_Chat_Exporter/__main__.py index 0a67026..9efe721 100644 --- a/Whatsapp_Chat_Exporter/__main__.py +++ b/Whatsapp_Chat_Exporter/__main__.py @@ -12,6 +12,7 @@ from Whatsapp_Chat_Exporter import extract, extract_iphone_media from Whatsapp_Chat_Exporter.data_model import ChatStore from Whatsapp_Chat_Exporter.utility import Crypt, DbType, check_update, import_from_json from argparse import ArgumentParser, SUPPRESS +from datetime import datetime from sys import exit try: from .__init__ import __version__ @@ -207,6 +208,18 @@ def main(): choices=range(-12, 15), help="Offset in hours (-12 to 14) for time displayed in the output" ) + parser.add_argument( + "--date", + dest="filter_date", + default=None, + help="The date filter in specific format (inclusive)" + ) + parser.add_argument( + "--date-format", + dest="filter_date_format", + default="%Y-%m-%d %H:%M", + help="The date format for the date filter." + ) args = parser.parse_args() # Check for updates @@ -232,6 +245,22 @@ def main(): if args.android and args.business: print("WhatsApp Business is only available on iOS for now.") exit(1) + if args.filter_date is not None: + if " - " in args.filter_date: + start, end = args.filter_date.split(" - ") + start = int(datetime.strptime(start, args.filter_date_format).timestamp()) + end = int(datetime.strptime(end, args.filter_date_format).timestamp()) + args.filter_date = f"BETWEEN {start}000 AND {end}000" + else: + _timestamp = int(datetime.strptime(args.filter_date[2:], args.filter_date_format).timestamp()) + if args.filter_date[:2] == "> ": + args.filter_date = f">= {_timestamp}000" + elif args.filter_date[:2] == "< ": + args.filter_date = f"<= {_timestamp}000" + else: + print("Unsupported date format. See https://wts.knugi.dev/filter.html") + exit(1) + data = {} @@ -338,9 +367,9 @@ def main(): if os.path.isfile(msg_db): with sqlite3.connect(msg_db) as db: db.row_factory = sqlite3.Row - messages(db, data, args.media, args.timezone_offset) - media(db, data, args.media) - vcard(db, data, args.media) + messages(db, data, args.media, args.timezone_offset, args.filter_date) + media(db, data, args.media, args.filter_date) + vcard(db, data, args.media, args.filter_date) if args.android: extract.calls(db, data, args.timezone_offset) if not args.no_html: diff --git a/Whatsapp_Chat_Exporter/extract.py b/Whatsapp_Chat_Exporter/extract.py index 26a2a5c..87fb551 100644 --- a/Whatsapp_Chat_Exporter/extract.py +++ b/Whatsapp_Chat_Exporter/extract.py @@ -165,18 +165,18 @@ def contacts(db, data): row = c.fetchone() -def messages(db, data, media_folder, timezone_offset): +def messages(db, data, media_folder, timezone_offset, range): # Get message history c = db.cursor() try: - c.execute("""SELECT count() FROM messages""") + c.execute(f"""SELECT count() FROM messages {f'WHERE timestamp {range}' if range is not None else ''}""") except sqlite3.OperationalError: - c.execute("""SELECT count() FROM message""") + c.execute(f"""SELECT count() FROM message {f'WHERE timestamp {range}' if range is not None else ''}""") total_row_number = c.fetchone()[0] print(f"Processing messages...(0/{total_row_number})", end="\r") try: - c.execute("""SELECT messages.key_remote_jid, + c.execute(f"""SELECT messages.key_remote_jid, messages._id, messages.key_from_me, messages.timestamp, @@ -226,12 +226,13 @@ def messages(db, data, media_folder, timezone_offset): LEFT JOIN receipt_user ON receipt_user.message_row_id = messages._id WHERE messages.key_remote_jid <> '-1' + {f'AND messages.timestamp {range}' if range is not None else ''} GROUP BY messages._id ORDER BY messages.timestamp ASC;""" ) except sqlite3.OperationalError: try: - c.execute("""SELECT jid_global.raw_string as key_remote_jid, + c.execute(f"""SELECT jid_global.raw_string as key_remote_jid, message._id, message.from_me as key_from_me, message.timestamp, @@ -291,6 +292,7 @@ def messages(db, data, media_folder, timezone_offset): LEFT JOIN receipt_user ON receipt_user.message_row_id = message._id WHERE key_remote_jid <> '-1' + {f'AND message.timestamp {range}' if range is not None else ''} GROUP BY message._id;""" ) except Exception as e: @@ -455,15 +457,26 @@ def messages(db, data, media_folder, timezone_offset): print(f"Processing messages...({total_row_number}/{total_row_number})", end="\r") -def media(db, data, media_folder): +def media(db, data, media_folder, range): # Get media c = db.cursor() - c.execute("""SELECT count() FROM message_media""") + try: + c.execute(f"""SELECT count() + FROM message_media + INNER JOIN messages + ON message_media.message_row_id = messages._id + {f'WHERE messages.timestamp {range}' if range is not None else ''}""") + except sqlite3.OperationalError: + c.execute(f"""SELECT count() + FROM message_media + INNER JOIN message + ON message_media.message_row_id = message._id + {f'WHERE message.timestamp {range}' if range is not None else ''}""") total_row_number = c.fetchone()[0] print(f"\nProcessing media...(0/{total_row_number})", end="\r") i = 0 try: - c.execute("""SELECT messages.key_remote_jid, + c.execute(f"""SELECT messages.key_remote_jid, message_row_id, file_path, message_url, @@ -477,10 +490,11 @@ def media(db, data, media_folder): LEFT JOIN media_hash_thumbnail ON message_media.file_hash = media_hash_thumbnail.media_hash WHERE jid.type <> 7 + {f'AND messages.timestamp {range}' if range is not None else ''} ORDER BY messages.key_remote_jid ASC""" ) except sqlite3.OperationalError: - c.execute("""SELECT jid.raw_string as key_remote_jid, + c.execute(f"""SELECT jid.raw_string as key_remote_jid, message_row_id, file_path, message_url, @@ -498,6 +512,7 @@ def media(db, data, media_folder): LEFT JOIN media_hash_thumbnail ON message_media.file_hash = media_hash_thumbnail.media_hash WHERE jid.type <> 7 + {f'AND message.timestamp {range}' if range is not None else ''} ORDER BY jid.raw_string ASC""" ) content = c.fetchone() @@ -547,20 +562,21 @@ def media(db, data, media_folder): f"Processing media...({total_row_number}/{total_row_number})", end="\r") -def vcard(db, data, media_folder): +def vcard(db, data, media_folder, range): c = db.cursor() try: - c.execute("""SELECT message_row_id, + c.execute(f"""SELECT message_row_id, messages.key_remote_jid, vcard, messages.media_name FROM messages_vcards INNER JOIN messages ON messages_vcards.message_row_id = messages._id + {f'WHERE messages.timestamp {range}' if range is not None else ''} ORDER BY messages.key_remote_jid ASC;""" ) except sqlite3.OperationalError: - c.execute("""SELECT message_row_id, + c.execute(f"""SELECT message_row_id, jid.raw_string as key_remote_jid, vcard, message.text_data as media_name @@ -571,6 +587,7 @@ def vcard(db, data, media_folder): ON chat._id = message.chat_row_id INNER JOIN jid ON jid._id = chat.jid_row_id + {f'WHERE message.timestamp {range}' if range is not None else ''} ORDER BY message.chat_row_id ASC;""" ) diff --git a/Whatsapp_Chat_Exporter/filter.html b/Whatsapp_Chat_Exporter/filter.html new file mode 100644 index 0000000..d9d980e --- /dev/null +++ b/Whatsapp_Chat_Exporter/filter.html @@ -0,0 +1,12 @@ + + + + + + + +

If the redirection doesn't work, you can find the documentation on filters at https://github.com/KnugiHK/WhatsApp-Chat-Exporter/wiki/Filters.

+ +