mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-04-24 06:51:39 +00:00
Display the metadata from the messages sent by "me" (#69)
For now, only the time for "delivered" (android & ios) and "read" (android only) is support.
This commit is contained in:
@@ -230,11 +230,8 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
|
|||||||
jid_old.raw_string as old_jid,
|
jid_old.raw_string as old_jid,
|
||||||
jid_new.raw_string as new_jid,
|
jid_new.raw_string as new_jid,
|
||||||
jid_global.type as jid_type,
|
jid_global.type as jid_type,
|
||||||
group_concat(receipt_user.receipt_timestamp) as receipt_timestamp,
|
COALESCE(receipt_user.receipt_timestamp, messages.received_timestamp) as received_timestamp,
|
||||||
group_concat(messages.received_timestamp) as received_timestamp,
|
COALESCE(receipt_user.read_timestamp, receipt_user.played_timestamp, messages.read_device_timestamp) as read_timestamp
|
||||||
group_concat(receipt_user.read_timestamp) as read_timestamp,
|
|
||||||
group_concat(receipt_user.played_timestamp) as played_timestamp,
|
|
||||||
group_concat(messages.read_device_timestamp) as read_device_timestamp
|
|
||||||
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
|
||||||
@@ -290,10 +287,8 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
|
|||||||
jid_old.raw_string as old_jid,
|
jid_old.raw_string as old_jid,
|
||||||
jid_new.raw_string as new_jid,
|
jid_new.raw_string as new_jid,
|
||||||
jid_global.type as jid_type,
|
jid_global.type as jid_type,
|
||||||
group_concat(receipt_user.receipt_timestamp) as receipt_timestamp,
|
COALESCE(receipt_user.receipt_timestamp, message.received_timestamp) as received_timestamp,
|
||||||
group_concat(message.received_timestamp) as received_timestamp,
|
COALESCE(receipt_user.read_timestamp, receipt_user.played_timestamp) as read_timestamp
|
||||||
group_concat(receipt_user.read_timestamp) as read_timestamp,
|
|
||||||
group_concat(receipt_user.played_timestamp) as played_timestamp
|
|
||||||
FROM message
|
FROM message
|
||||||
LEFT JOIN message_quoted
|
LEFT JOIN message_quoted
|
||||||
ON message_quoted.message_row_id = message._id
|
ON message_quoted.message_row_id = message._id
|
||||||
@@ -361,7 +356,9 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
|
|||||||
time=content["timestamp"],
|
time=content["timestamp"],
|
||||||
key_id=content["key_id"],
|
key_id=content["key_id"],
|
||||||
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET,
|
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET,
|
||||||
message_type=content["media_wa_type"]
|
message_type=content["media_wa_type"],
|
||||||
|
received_timestamp=content["received_timestamp"],
|
||||||
|
read_timestamp=content["read_timestamp"]
|
||||||
)
|
)
|
||||||
if isinstance(content["data"], bytes):
|
if isinstance(content["data"], bytes):
|
||||||
message.data = ("The message is binary data and its base64 is "
|
message.data = ("The message is binary data and its base64 is "
|
||||||
@@ -736,7 +733,9 @@ def calls(db, data, timezone_offset, filter_chat):
|
|||||||
timestamp=content["timestamp"],
|
timestamp=content["timestamp"],
|
||||||
time=content["timestamp"],
|
time=content["timestamp"],
|
||||||
key_id=content["call_id"],
|
key_id=content["call_id"],
|
||||||
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET
|
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET,
|
||||||
|
received_timestamp=None, # TODO: Add timestamp
|
||||||
|
read_timestamp=None # TODO: Add timestamp
|
||||||
)
|
)
|
||||||
_jid = content["raw_string"]
|
_jid = content["raw_string"]
|
||||||
name = data[_jid].name if _jid in data else content["chat_subject"] or None
|
name = data[_jid].name if _jid in data else content["chat_subject"] or None
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ from datetime import datetime, tzinfo, timedelta
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
class Timing():
|
||||||
|
def __init__(self, timezone_offset: Union[int, None]):
|
||||||
|
self.timezone_offset = timezone_offset
|
||||||
|
|
||||||
|
def format_timestamp(self, timestamp, format):
|
||||||
|
if timestamp:
|
||||||
|
timestamp = timestamp / 1000 if timestamp > 9999999999 else timestamp
|
||||||
|
return datetime.fromtimestamp(timestamp, TimeZone(self.timezone_offset)).strftime(format)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TimeZone(tzinfo):
|
class TimeZone(tzinfo):
|
||||||
def __init__(self, offset):
|
def __init__(self, offset):
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
@@ -65,11 +77,23 @@ class ChatStore():
|
|||||||
|
|
||||||
|
|
||||||
class Message():
|
class Message():
|
||||||
def __init__(self, from_me: Union[bool,int], timestamp: int, time: Union[int,float,str], key_id: int, timezone_offset: int = 0, message_type: int = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
from_me: Union[bool,int],
|
||||||
|
timestamp: int,
|
||||||
|
time: Union[int,float,str],
|
||||||
|
key_id: int,
|
||||||
|
received_timestamp: int,
|
||||||
|
read_timestamp: int,
|
||||||
|
timezone_offset: int = 0,
|
||||||
|
message_type: int = None
|
||||||
|
):
|
||||||
self.from_me = bool(from_me)
|
self.from_me = bool(from_me)
|
||||||
self.timestamp = timestamp / 1000 if timestamp > 9999999999 else timestamp
|
self.timestamp = timestamp / 1000 if timestamp > 9999999999 else timestamp
|
||||||
|
timing = Timing(timezone_offset)
|
||||||
if isinstance(time, int) or isinstance(time, float):
|
if isinstance(time, int) or isinstance(time, float):
|
||||||
self.time = datetime.fromtimestamp(self.timestamp, TimeZone(timezone_offset)).strftime("%H:%M")
|
self.time = timing.format_timestamp(self.timestamp, "%H:%M")
|
||||||
elif isinstance(time, str):
|
elif isinstance(time, str):
|
||||||
self.time = time
|
self.time = time
|
||||||
else:
|
else:
|
||||||
@@ -81,7 +105,9 @@ class Message():
|
|||||||
self.sender = None
|
self.sender = None
|
||||||
self.safe = False
|
self.safe = False
|
||||||
self.mime = None
|
self.mime = None
|
||||||
self.message_type = message_type
|
self.message_type = message_type,
|
||||||
|
self.received_timestamp = timing.format_timestamp(received_timestamp, "%Y/%m/%d %H:%M")
|
||||||
|
self.read_timestamp = timing.format_timestamp(read_timestamp, "%Y/%m/%d %H:%M")
|
||||||
# Extra
|
# Extra
|
||||||
self.reply = None
|
self.reply = None
|
||||||
self.quoted_data = None
|
self.quoted_data = None
|
||||||
|
|||||||
@@ -114,7 +114,8 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
|
|||||||
ZWAGROUPMEMBER.ZMEMBERJID,
|
ZWAGROUPMEMBER.ZMEMBERJID,
|
||||||
ZMETADATA,
|
ZMETADATA,
|
||||||
ZSTANZAID,
|
ZSTANZAID,
|
||||||
ZGROUPINFO
|
ZGROUPINFO,
|
||||||
|
ZSENTDATE
|
||||||
FROM ZWAMESSAGE
|
FROM ZWAMESSAGE
|
||||||
LEFT JOIN ZWAGROUPMEMBER
|
LEFT JOIN ZWAGROUPMEMBER
|
||||||
ON ZWAMESSAGE.ZGROUPMEMBER = ZWAGROUPMEMBER.Z_PK
|
ON ZWAMESSAGE.ZGROUPMEMBER = ZWAGROUPMEMBER.Z_PK
|
||||||
@@ -152,7 +153,9 @@ def messages(db, data, media_folder, timezone_offset, filter_date, filter_chat,
|
|||||||
time=ts,
|
time=ts,
|
||||||
key_id=content["ZSTANZAID"][:17],
|
key_id=content["ZSTANZAID"][:17],
|
||||||
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET,
|
timezone_offset=timezone_offset if timezone_offset else CURRENT_TZ_OFFSET,
|
||||||
message_type=content["ZMESSAGETYPE"]
|
message_type=content["ZMESSAGETYPE"],
|
||||||
|
received_timestamp=APPLE_TIME + content["ZSENTDATE"] if content["ZSENTDATE"] else None,
|
||||||
|
read_timestamp=None # TODO: Add timestamp
|
||||||
)
|
)
|
||||||
invalid = False
|
invalid = False
|
||||||
if is_group_message and content["ZISFROMME"] == 0:
|
if is_group_message and content["ZISFROMME"] == 0:
|
||||||
|
|||||||
@@ -123,6 +123,10 @@
|
|||||||
.reply-box:active {
|
.reply-box:active {
|
||||||
background-color:rgb(200 202 205 / var(--tw-bg-opacity, 1));
|
background-color:rgb(200 202 205 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
.info-box-tooltip {
|
||||||
|
--tw-translate-x: -50%;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function search(event) {
|
function search(event) {
|
||||||
@@ -207,7 +211,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<!--Actual messages-->
|
<!--Actual messages-->
|
||||||
{% if msg.from_me == true %}
|
{% if msg.from_me == true %}
|
||||||
<div class="flex justify-end" id="{{ msg.key_id }}">
|
<div class="flex justify-end items-center group" id="{{ msg.key_id }}">
|
||||||
|
<div class="opacity-0 group-hover:opacity-100 transition-opacity duration-200 relative mr-2">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="relative group/tooltip">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#8696a0] hover:text-[#54656f] cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<use href="#info-icon"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="absolute bottom-full info-box-tooltip mb-2 hidden group-hover/tooltip:block z-50">
|
||||||
|
<div class="bg-black text-white text-xs rounded py-1 px-2 whitespace-nowrap">
|
||||||
|
Delivered at {{msg.received_timestamp or 'unknown'}}
|
||||||
|
{% if msg.read_timestamp is not none %}
|
||||||
|
<br>Read at {{ msg.read_timestamp }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-full right-3 -mt-1 border-4 border-transparent border-t-black"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="bg-whatsapp-light rounded-lg p-2 max-w-[80%] shadow-sm">
|
<div class="bg-whatsapp-light rounded-lg p-2 max-w-[80%] shadow-sm">
|
||||||
{% if msg.reply is not none %}
|
{% if msg.reply is not none %}
|
||||||
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
||||||
@@ -268,7 +290,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex justify-start" id="{{ msg.key_id }}">
|
<div class="flex justify-start items-center group" id="{{ msg.key_id }}">
|
||||||
<div class="bg-white rounded-lg p-2 max-w-[80%] shadow-sm">
|
<div class="bg-white rounded-lg p-2 max-w-[80%] shadow-sm">
|
||||||
{% if msg.reply is not none %}
|
{% if msg.reply is not none %}
|
||||||
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
||||||
@@ -335,6 +357,21 @@
|
|||||||
<span class="flex-shrink-0">{{ msg.time }}</span>
|
<span class="flex-shrink-0">{{ msg.time }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div class="opacity-0 group-hover:opacity-100 transition-opacity duration-200 relative ml-2">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="relative group/tooltip">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#8696a0] hover:text-[#54656f] cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<use href="#info-icon"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="absolute bottom-full info-box-tooltip mb-2 hidden group-hover/tooltip:block z-50">
|
||||||
|
<div class="bg-black text-white text-xs rounded py-1 px-2 whitespace-nowrap">
|
||||||
|
Received at {{msg.received_timestamp or 'unknown'}}
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-full right-3 ml-1 border-4 border-transparent border-t-black"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -348,6 +385,12 @@
|
|||||||
<br>
|
<br>
|
||||||
Portions of this page are reproduced from <a href="https://web.dev/articles/lazy-loading-video">work</a> created and <a href="https://developers.google.com/readme/policies">shared by Google</a> and used according to terms described in the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>.
|
Portions of this page are reproduced from <a href="https://web.dev/articles/lazy-loading-video">work</a> created and <a href="https://developers.google.com/readme/policies">shared by Google</a> and used according to terms described in the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>.
|
||||||
</footer>
|
</footer>
|
||||||
|
<svg style="display: none;">
|
||||||
|
<!-- Tooltip info icon -->
|
||||||
|
<symbol id="info-icon" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user