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:
KnugiHK
2025-02-09 18:44:18 +08:00
parent aaeff80547
commit cfe04c8c0b
4 changed files with 89 additions and 18 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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>