mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-02-23 09:04:04 +00:00
Switch the default template to the WhatsApp-alike them
The old telegram theme can still be applied with the `--old-theme` option
This commit is contained in:
@@ -185,8 +185,8 @@ def setup_argument_parser() -> ArgumentParser:
|
|||||||
help="Do not render avatar in HTML output"
|
help="Do not render avatar in HTML output"
|
||||||
)
|
)
|
||||||
html_group.add_argument(
|
html_group.add_argument(
|
||||||
"--experimental-new-theme", dest="whatsapp_theme", default=False, action='store_true',
|
"--old-theme", dest="telegram_theme", default=False, action='store_true',
|
||||||
help="Use the newly designed WhatsApp-alike theme"
|
help="Use the old Telegram-alike theme"
|
||||||
)
|
)
|
||||||
html_group.add_argument(
|
html_group.add_argument(
|
||||||
"--headline", dest="headline", default="Chat history with ??",
|
"--headline", dest="headline", default="Chat history with ??",
|
||||||
@@ -359,8 +359,8 @@ def validate_args(parser: ArgumentParser, args) -> None:
|
|||||||
args.key = getpass("Enter your encryption key: ")
|
args.key = getpass("Enter your encryption key: ")
|
||||||
|
|
||||||
# Theme validation
|
# Theme validation
|
||||||
if args.whatsapp_theme:
|
if args.telegram_theme:
|
||||||
args.template = "whatsapp_new.html"
|
args.template = "whatsapp_old.html"
|
||||||
|
|
||||||
# Chat filter validation
|
# Chat filter validation
|
||||||
if args.filter_chat_include is not None and args.filter_chat_exclude is not None:
|
if args.filter_chat_include is not None and args.filter_chat_exclude is not None:
|
||||||
@@ -628,7 +628,7 @@ def create_output_files(args, data: ChatCollection) -> None:
|
|||||||
args.offline,
|
args.offline,
|
||||||
args.size,
|
args.size,
|
||||||
args.no_avatar,
|
args.no_avatar,
|
||||||
args.whatsapp_theme,
|
args.telegram_theme,
|
||||||
args.headline
|
args.headline
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -713,7 +713,7 @@ def process_exported_chat(args, data: ChatCollection) -> None:
|
|||||||
args.offline,
|
args.offline,
|
||||||
args.size,
|
args.size,
|
||||||
args.no_avatar,
|
args.no_avatar,
|
||||||
args.whatsapp_theme,
|
args.telegram_theme,
|
||||||
args.headline
|
args.headline
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -779,7 +779,7 @@ def main():
|
|||||||
args.offline,
|
args.offline,
|
||||||
args.size,
|
args.size,
|
||||||
args.no_avatar,
|
args.no_avatar,
|
||||||
args.whatsapp_theme,
|
args.telegram_theme,
|
||||||
args.headline
|
args.headline
|
||||||
)
|
)
|
||||||
elif args.exported:
|
elif args.exported:
|
||||||
|
|||||||
@@ -1,329 +1,467 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Whatsapp - {{ name }}</title>
|
<title>Whatsapp - {{ name }}</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="{{w3css}}">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<script>
|
||||||
html, body {
|
tailwind.config = {
|
||||||
font-size: 12px;
|
theme: {
|
||||||
scroll-behavior: smooth;
|
extend: {
|
||||||
}
|
colors: {
|
||||||
header {
|
whatsapp: {
|
||||||
position: fixed;
|
light: '#e7ffdb',
|
||||||
z-index: 20;
|
DEFAULT: '#25D366',
|
||||||
border-bottom: 2px solid #e3e6e7;
|
dark: '#075E54',
|
||||||
font-size: 2em;
|
chat: '#efeae2',
|
||||||
font-weight: bolder;
|
'chat-light': '#f0f2f5',
|
||||||
background-color: white;
|
}
|
||||||
padding: 20px 0 20px 0;
|
}
|
||||||
}
|
}
|
||||||
footer {
|
}
|
||||||
border-top: 2px solid #e3e6e7;
|
}
|
||||||
padding: 20px 0 20px 0;
|
</script>
|
||||||
}
|
<style>
|
||||||
article {
|
body, html {
|
||||||
width:500px;
|
height: 100%;
|
||||||
margin:100px auto;
|
margin: 0;
|
||||||
z-index:10;
|
padding: 0;
|
||||||
font-size: 15px;
|
scroll-behavior: smooth !important;
|
||||||
word-wrap: break-word;
|
}
|
||||||
}
|
.chat-list {
|
||||||
img, video {
|
height: calc(100vh - 120px);
|
||||||
max-width:100%;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
div.reply{
|
.message-list {
|
||||||
font-size: 13px;
|
height: calc(100vh - 90px);
|
||||||
text-decoration: none;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
div:target::before {
|
@media (max-width: 640px) {
|
||||||
content: '';
|
.chat-list, .message-list {
|
||||||
display: block;
|
height: calc(100vh - 108px);
|
||||||
height: 115px;
|
}
|
||||||
margin-top: -115px;
|
}
|
||||||
visibility: hidden;
|
header {
|
||||||
}
|
position: fixed;
|
||||||
div:target {
|
z-index: 20;
|
||||||
border-style: solid;
|
border-bottom: 2px solid #e3e6e7;
|
||||||
border-width: 2px;
|
font-size: 2em;
|
||||||
animation: border-blink 0.5s steps(1) 5;
|
font-weight: bolder;
|
||||||
border-color: rgba(0,0,0,0)
|
background-color: white;
|
||||||
}
|
padding: 20px 0 20px 0;
|
||||||
table {
|
}
|
||||||
width: 100%;
|
footer {
|
||||||
}
|
margin-top: 10px;
|
||||||
@keyframes border-blink {
|
border-top: 2px solid #e3e6e7;
|
||||||
0% {
|
padding: 20px 0 20px 0;
|
||||||
border-color: #2196F3;
|
}
|
||||||
}
|
article {
|
||||||
50% {
|
width:430px;
|
||||||
border-color: rgba(0,0,0,0);
|
margin: auto;
|
||||||
}
|
z-index:10;
|
||||||
}
|
font-size: 15px;
|
||||||
.avatar {
|
word-wrap: break-word;
|
||||||
border-radius:50%;
|
}
|
||||||
overflow:hidden;
|
img, video, audio{
|
||||||
max-width: 64px;
|
max-width:100%;
|
||||||
max-height: 64px;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.name {
|
div.reply{
|
||||||
color: #3892da;
|
font-size: 13px;
|
||||||
}
|
text-decoration: none;
|
||||||
.pad-left-10 {
|
}
|
||||||
padding-left: 10px;
|
div:target::before {
|
||||||
}
|
content: '';
|
||||||
.pad-right-10 {
|
display: block;
|
||||||
padding-right: 10px;
|
height: 115px;
|
||||||
}
|
margin-top: -115px;
|
||||||
.reply_link {
|
visibility: hidden;
|
||||||
color: #168acc;
|
}
|
||||||
}
|
div:target {
|
||||||
.blue {
|
animation: 3s highlight;
|
||||||
color: #70777a;
|
}
|
||||||
}
|
.avatar {
|
||||||
.sticker {
|
border-radius:50%;
|
||||||
max-width: 100px !important;
|
overflow:hidden;
|
||||||
max-height: 100px !important;
|
max-width: 64px;
|
||||||
}
|
max-height: 64px;
|
||||||
</style>
|
}
|
||||||
<base href="{{ media_base }}" target="_blank">
|
.name {
|
||||||
</head>
|
color: #3892da;
|
||||||
<body>
|
}
|
||||||
<header class="w3-center w3-top">
|
.pad-left-10 {
|
||||||
{{ headline }}
|
padding-left: 10px;
|
||||||
{% if status is not none %}
|
}
|
||||||
<br>
|
.pad-right-10 {
|
||||||
<span class="w3-small">{{ status }}</span>
|
padding-right: 10px;
|
||||||
{% endif %}
|
}
|
||||||
</header>
|
.reply_link {
|
||||||
<article class="w3-container">
|
color: #168acc;
|
||||||
<div class="table">
|
}
|
||||||
{% set last = {'last': 946688461.001} %}
|
.blue {
|
||||||
{% for msg in msgs -%}
|
color: #70777a;
|
||||||
<div class="w3-row w3-padding-small w3-margin-bottom" id="{{ msg.key_id }}">
|
}
|
||||||
{% if determine_day(last.last, msg.timestamp) is not none %}
|
.sticker {
|
||||||
<div class="w3-center w3-padding-16 blue">{{ determine_day(last.last, msg.timestamp) }}</div>
|
max-width: 100px !important;
|
||||||
{% if last.update({'last': msg.timestamp}) %}{% endif %}
|
max-height: 100px !important;
|
||||||
{% endif %}
|
}
|
||||||
{% if msg.from_me == true %}
|
@keyframes highlight {
|
||||||
<div class="w3-row">
|
from {
|
||||||
<div class="w3-left blue">{{ msg.time }}</div>
|
background-color: rgba(37, 211, 102, 0.1);
|
||||||
<div class="name w3-right-align pad-left-10">You</div>
|
}
|
||||||
</div>
|
to {
|
||||||
<div class="w3-row">
|
background-color: transparent;
|
||||||
{% if not no_avatar and my_avatar is not none %}
|
}
|
||||||
<div class="w3-col m10 l10">
|
}
|
||||||
{% else %}
|
.search-input {
|
||||||
<div class="w3-col m12 l12">
|
transform: translateY(-100%);
|
||||||
{% endif %}
|
transition: transform 0.3s ease-in-out;
|
||||||
<div class="w3-right-align">
|
}
|
||||||
{% if msg.reply is not none %}
|
.search-input.active {
|
||||||
<div class="reply">
|
transform: translateY(0);
|
||||||
<span class="blue">Replying to </span>
|
}
|
||||||
<a href="#{{msg.reply}}" target="_self" class="reply_link no-base">
|
.reply-box:active {
|
||||||
{% if msg.quoted_data is not none %}
|
background-color:rgb(200 202 205 / var(--tw-bg-opacity, 1));
|
||||||
"{{msg.quoted_data}}"
|
}
|
||||||
{% else %}
|
.info-box-tooltip {
|
||||||
this message
|
--tw-translate-x: -50%;
|
||||||
{% endif %}
|
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));
|
||||||
</a>
|
}
|
||||||
</div>
|
</style>
|
||||||
{% endif %}
|
<script>
|
||||||
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
function search(event) {
|
||||||
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
keywords = document.getElementById("mainHeaderSearchInput").value;
|
||||||
{% if msg.safe %}
|
hits = [];
|
||||||
<p>{{ msg.data | safe or 'Not supported WhatsApp internal message' }}</p>
|
document.querySelectorAll(".message-text").forEach(elem => {
|
||||||
{% else %}
|
if (elem.innerText.trim().includes(keywords)){
|
||||||
<p>{{ msg.data or 'Not supported WhatsApp internal message' }}</p>
|
hits.push(elem.parentElement.parentElement.id);
|
||||||
{% endif %}
|
}
|
||||||
</div>
|
})
|
||||||
{% if msg.caption is not none %}
|
console.log(hits);
|
||||||
<div class="w3-container">
|
}
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
</script>
|
||||||
</div>
|
<base href="{{ media_base }}" target="_blank">
|
||||||
{% endif %}
|
</head>
|
||||||
{% else %}
|
<body>
|
||||||
{% if msg.media == false %}
|
<article class="h-screen bg-whatsapp-chat-light">
|
||||||
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
<div class="w-full flex flex-col">
|
||||||
{% else %}
|
<div class="p-3 bg-whatsapp-dark flex items-center justify-between border-l border-[#d1d7db]">
|
||||||
{% if "image/" in msg.mime %}
|
<div class="flex items-center">
|
||||||
<a href="{{ msg.data }}">
|
{% if not no_avatar %}
|
||||||
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
<div class="w3-col m2 l2">
|
||||||
</a>
|
{% if their_avatar is not none %}
|
||||||
{% elif "audio/" in msg.mime %}
|
<a href="{{ their_avatar }}"><img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="w-10 h-10 rounded-full mr-3" loading="lazy"></a>
|
||||||
<audio controls="controls" autobuffer="autobuffer">
|
{% else %}
|
||||||
<source src="{{ msg.data }}" />
|
<img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="w-10 h-10 rounded-full mr-3" loading="lazy">
|
||||||
</audio>
|
{% endif %}
|
||||||
{% elif "video/" in msg.mime %}
|
</div>
|
||||||
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
{% endif %}
|
||||||
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
<div>
|
||||||
</video>
|
<h2 class="text-white font-medium">{{ headline }}</h2>
|
||||||
{% elif "/" in msg.mime %}
|
{% if status is not none %}<p class="text-[#8696a0] text-xs">{{ status }}</p>{% endif %}
|
||||||
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
</div>
|
||||||
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
</div>
|
||||||
</div>
|
<div class="flex space-x-4">
|
||||||
{% else %}
|
<!-- <button id="searchButton">
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#aebac1]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
{% endif %}
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
{% if msg.caption is not none %}
|
</svg>
|
||||||
<div class="w3-container">
|
</button> -->
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#aebac1]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
</div>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||||
{% endif %}
|
</svg> -->
|
||||||
{% endif %}
|
{% if previous %}
|
||||||
{% endif %}
|
<a href="./{{ previous }}" target="_self">
|
||||||
</div>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#aebac1]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
</div>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 5l-7 7 7 7" />
|
||||||
{% if not no_avatar and my_avatar is not none %}
|
</svg>
|
||||||
<div class="w3-col m2 l2 pad-left-10">
|
</a>
|
||||||
<a href="{{ my_avatar }}">
|
{% endif %}
|
||||||
<img src="{{ my_avatar }}" onerror="this.style.display='none'" class="avatar" loading="lazy">
|
{% if next %}
|
||||||
</a>
|
<a href="./{{ next }}" target="_self">
|
||||||
</div>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#aebac1]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
{% endif %}
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||||
</div>
|
</svg>
|
||||||
{% else %}
|
</a>
|
||||||
<div class="w3-row">
|
{% endif %}
|
||||||
<div class="w3-left pad-right-10 name">
|
</div>
|
||||||
{% if msg.sender is not none %}
|
<!-- Search Input Overlay -->
|
||||||
{{ msg.sender }}
|
<div id="mainSearchInput" class="search-input absolute article top-0 bg-whatsapp-dark p-3 flex items-center space-x-3">
|
||||||
{% else %}
|
<button id="closeMainSearch" class="text-[#aebac1]">
|
||||||
{{ name }}
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
{% endif %}
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||||
</div>
|
</svg>
|
||||||
<div class="w3-right-align blue">{{ msg.time }}</div>
|
</button>
|
||||||
</div>
|
<input type="text" placeholder="Search..." class="flex-1 bg-[#1f2c34] text-white rounded-lg px-3 py-1 focus:outline-none" id="mainHeaderSearchInput" onkeyup="search(event)">
|
||||||
<div class="w3-row">
|
</div>
|
||||||
{% if not no_avatar %}
|
</div>
|
||||||
<div class="w3-col m2 l2">
|
</div>
|
||||||
{% if their_avatar is not none %}
|
<div class="flex-1 p-5 message-list">
|
||||||
<a href="{{ their_avatar }}"><img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="avatar" loading="lazy"></a>
|
<div class="flex flex-col space-y-2">
|
||||||
{% else %}
|
<!--Date-->
|
||||||
<img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="avatar" loading="lazy">
|
{% set last = {'last': 946688461.001} %}
|
||||||
{% endif %}
|
{% for msg in msgs -%}
|
||||||
</div>
|
{% if determine_day(last.last, msg.timestamp) is not none %}
|
||||||
<div class="w3-col m10 l10">
|
<div class="flex justify-center">
|
||||||
{% else %}
|
<div class="bg-[#e1f2fb] rounded-lg px-2 py-1 text-xs text-[#54656f]">
|
||||||
<div class="w3-col m12 l12">
|
{{ determine_day(last.last, msg.timestamp) }}
|
||||||
{% endif %}
|
</div>
|
||||||
<div class="w3-left-align">
|
</div>
|
||||||
{% if msg.reply is not none %}
|
{% if last.update({'last': msg.timestamp}) %}{% endif %}
|
||||||
<div class="reply">
|
{% endif %}
|
||||||
<span class="blue">Replying to </span>
|
<!--Actual messages-->
|
||||||
<a href="#{{msg.reply}}" target="_self" class="reply_link no-base">
|
{% if msg.from_me == true %}
|
||||||
{% if msg.quoted_data is not none %}
|
<div class="flex justify-end items-center group" id="{{ msg.key_id }}">
|
||||||
"{{msg.quoted_data}}"
|
<div class="opacity-0 group-hover:opacity-100 transition-opacity duration-200 relative mr-2">
|
||||||
{% else %}
|
<div class="relative">
|
||||||
this message
|
<div class="relative group/tooltip">
|
||||||
{% endif %}
|
<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">
|
||||||
</a>
|
<use href="#info-icon"></use>
|
||||||
</div>
|
</svg>
|
||||||
{% endif %}
|
<div class="absolute bottom-full info-box-tooltip mb-2 hidden group-hover/tooltip:block z-50">
|
||||||
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
<div class="bg-black text-white text-xs rounded py-1 px-2 whitespace-nowrap">
|
||||||
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
Delivered at {{msg.received_timestamp or 'unknown'}}
|
||||||
{% if msg.safe %}
|
{% if msg.read_timestamp is not none %}
|
||||||
<p>{{ msg.data | safe or 'Not supported WhatsApp internal message' }}</p>
|
<br>Read at {{ msg.read_timestamp }}
|
||||||
{% else %}
|
{% endif %}
|
||||||
<p>{{ msg.data or 'Not supported WhatsApp internal message' }}</p>
|
</div>
|
||||||
{% endif %}
|
<div class="absolute top-full right-3 -mt-1 border-4 border-transparent border-t-black"></div>
|
||||||
</div>
|
</div>
|
||||||
{% if msg.caption is not none %}
|
</div>
|
||||||
<div class="w3-container">
|
</div>
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
</div>
|
||||||
</div>
|
<div class="bg-whatsapp-light rounded-lg p-2 max-w-[80%] shadow-sm">
|
||||||
{% endif %}
|
{% if msg.reply is not none %}
|
||||||
{% else %}
|
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
||||||
{% if msg.media == false %}
|
<div class="mb-2 p-1 bg-whatsapp-chat-light rounded border-l-4 border-whatsapp text-sm reply-box">
|
||||||
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
<p class="text-whatsapp font-medium text-xs">Replying to</p>
|
||||||
{% else %}
|
<p class="text-[#111b21] text-xs truncate">
|
||||||
{% if "image/" in msg.mime %}
|
{% if msg.quoted_data is not none %}
|
||||||
<a href="{{ msg.data }}">
|
"{{msg.quoted_data}}"
|
||||||
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
{% else %}
|
||||||
</a>
|
this message
|
||||||
{% elif "audio/" in msg.mime %}
|
{% endif %}
|
||||||
<audio controls="controls" autobuffer="autobuffer">
|
</p>
|
||||||
<source src="{{ msg.data }}" />
|
</div>
|
||||||
</audio>
|
</a>
|
||||||
{% elif "video/" in msg.mime %}
|
{% endif %}
|
||||||
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
<p class="text-[#111b21] text-sm message-text">
|
||||||
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
||||||
</video>
|
<div class="flex justify-center mb-2">
|
||||||
{% elif "/" in msg.mime %}
|
<div class="bg-[#FFF3C5] rounded-lg px-3 py-2 text-sm text-[#856404] flex items-center">
|
||||||
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
{% if msg.safe %}
|
||||||
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
{{ msg.data | safe or 'Not supported WhatsApp internal message' }}
|
||||||
</div>
|
{% else %}
|
||||||
{% else %}
|
{{ msg.data or 'Not supported WhatsApp internal message' }}
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
{% endif %}
|
||||||
{% endif %}
|
</div>
|
||||||
{% if msg.caption is not none %}
|
</div>
|
||||||
<div class="w3-container">
|
{% if msg.caption is not none %}
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
<p>{{ msg.caption | urlize(none, true, '_blank') }}</p>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% else %}
|
||||||
{% endif %}
|
{% if msg.media == false %}
|
||||||
{% endif %}
|
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
||||||
</div>
|
{% else %}
|
||||||
</div>
|
{% if "image/" in msg.mime %}
|
||||||
</div>
|
<a href="{{ msg.data }}">
|
||||||
{% endif %}
|
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
||||||
</div>
|
</a>
|
||||||
{% endfor %}
|
{% elif "audio/" in msg.mime %}
|
||||||
</div>
|
<audio controls="controls" autobuffer="autobuffer">
|
||||||
</article>
|
<source src="{{ msg.data }}" />
|
||||||
<footer class="w3-center">
|
</audio>
|
||||||
<h2>
|
{% elif "video/" in msg.mime %}
|
||||||
{% if previous %}
|
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
||||||
<a href="./{{ previous }}" target="_self">Previous</a>
|
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
||||||
{% endif %}
|
</video>
|
||||||
<h2>
|
{% elif "/" in msg.mime %}
|
||||||
{% if next %}
|
The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a>
|
||||||
<a href="./{{ next }}" target="_self">Next</a>
|
{% else %}
|
||||||
{% else %}
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
End of History
|
{% endif %}
|
||||||
{% endif %}
|
{% if msg.caption is not none %}
|
||||||
</h2>
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
<br>
|
{% endif %}
|
||||||
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>.
|
{% endif %}
|
||||||
</footer>
|
{% endif %}
|
||||||
<script>
|
</p>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
<p class="text-[10px] text-[#667781] text-right mt-1">{{ msg.time }}</p>
|
||||||
var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<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">
|
||||||
|
{% if msg.reply is not none %}
|
||||||
|
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
||||||
|
<div class="mb-2 p-1 bg-whatsapp-chat-light rounded border-l-4 border-whatsapp text-sm reply-box">
|
||||||
|
<p class="text-whatsapp font-medium text-xs">Replying to</p>
|
||||||
|
<p class="text-[#808080] text-xs truncate">
|
||||||
|
{% if msg.quoted_data is not none %}
|
||||||
|
{{msg.quoted_data}}
|
||||||
|
{% else %}
|
||||||
|
this message
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-[#111b21] text-sm">
|
||||||
|
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
||||||
|
<div class="flex justify-center mb-2">
|
||||||
|
<div class="bg-[#FFF3C5] rounded-lg px-3 py-2 text-sm text-[#856404] flex items-center">
|
||||||
|
{% if msg.safe %}
|
||||||
|
{{ msg.data | safe or 'Not supported WhatsApp internal message' }}
|
||||||
|
{% else %}
|
||||||
|
{{ msg.data or 'Not supported WhatsApp internal message' }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
<p>{{ msg.caption | urlize(none, true, '_blank') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if msg.media == false %}
|
||||||
|
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
||||||
|
{% else %}
|
||||||
|
{% if "image/" in msg.mime %}
|
||||||
|
<a href="{{ msg.data }}">
|
||||||
|
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
||||||
|
</a>
|
||||||
|
{% elif "audio/" in msg.mime %}
|
||||||
|
<audio controls="controls" autobuffer="autobuffer">
|
||||||
|
<source src="{{ msg.data }}" />
|
||||||
|
</audio>
|
||||||
|
{% elif "video/" in msg.mime %}
|
||||||
|
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
||||||
|
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
||||||
|
</video>
|
||||||
|
{% elif "/" in msg.mime %}
|
||||||
|
The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a>
|
||||||
|
{% else %}
|
||||||
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-baseline text-[10px] text-[#667781] mt-1 gap-2">
|
||||||
|
<span class="flex-shrink-0">
|
||||||
|
{% if msg.sender is not none %}
|
||||||
|
{{ msg.sender }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="flex-grow min-w-[4px]"></span>
|
||||||
|
<span class="flex-shrink-0">{{ msg.time }}</span>
|
||||||
|
</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>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<h2 class="text-center">
|
||||||
|
{% if not next %}
|
||||||
|
End of History
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
<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>.
|
||||||
|
</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>
|
||||||
|
</article>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
// Search functionality
|
||||||
|
const searchButton = document.getElementById('searchButton');
|
||||||
|
const mainSearchInput = document.getElementById('mainSearchInput');
|
||||||
|
const closeMainSearch = document.getElementById('closeMainSearch');
|
||||||
|
const mainHeaderSearchInput = document.getElementById('mainHeaderSearchInput');
|
||||||
|
|
||||||
if ("IntersectionObserver" in window) {
|
// Function to show search input
|
||||||
var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
|
const showSearch = () => {
|
||||||
entries.forEach(function(video) {
|
mainSearchInput.classList.add('active');
|
||||||
if (video.isIntersecting) {
|
mainHeaderSearchInput.focus();
|
||||||
for (var source in video.target.children) {
|
};
|
||||||
var videoSource = video.target.children[source];
|
|
||||||
if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
|
|
||||||
videoSource.src = videoSource.dataset.src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video.target.load();
|
// Function to hide search input
|
||||||
video.target.classList.remove("lazy");
|
const hideSearch = () => {
|
||||||
lazyVideoObserver.unobserve(video.target);
|
mainSearchInput.classList.remove('active');
|
||||||
}
|
mainHeaderSearchInput.value = '';
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
lazyVideos.forEach(function(lazyVideo) {
|
// Event listeners
|
||||||
lazyVideoObserver.observe(lazyVideo);
|
searchButton.addEventListener('click', showSearch);
|
||||||
});
|
closeMainSearch.addEventListener('click', hideSearch);
|
||||||
}
|
|
||||||
});
|
// Handle ESC key
|
||||||
</script>
|
document.addEventListener('keydown', (event) => {
|
||||||
<script>
|
if (event.key === 'Escape' && mainSearchInput.classList.contains('active')) {
|
||||||
// Prevent the <base> tag from affecting links with the class "no-base"
|
hideSearch();
|
||||||
document.querySelectorAll('.no-base').forEach(link => {
|
}
|
||||||
link.addEventListener('click', function(event) {
|
});
|
||||||
const href = this.getAttribute('href');
|
</script>
|
||||||
if (href.startsWith('#')) {
|
<script>
|
||||||
window.location.hash = href;
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
event.preventDefault();
|
var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
|
||||||
}
|
|
||||||
});
|
if ("IntersectionObserver" in window) {
|
||||||
});
|
var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
|
||||||
</script>
|
entries.forEach(function(video) {
|
||||||
</body>
|
if (video.isIntersecting) {
|
||||||
|
for (var source in video.target.children) {
|
||||||
|
var videoSource = video.target.children[source];
|
||||||
|
if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
|
||||||
|
videoSource.src = videoSource.dataset.src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video.target.load();
|
||||||
|
video.target.classList.remove("lazy");
|
||||||
|
lazyVideoObserver.unobserve(video.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lazyVideos.forEach(function(lazyVideo) {
|
||||||
|
lazyVideoObserver.observe(lazyVideo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Prevent the <base> tag from affecting links with the class "no-base"
|
||||||
|
document.querySelectorAll('.no-base').forEach(link => {
|
||||||
|
link.addEventListener('click', function(event) {
|
||||||
|
const href = this.getAttribute('href');
|
||||||
|
if (href.startsWith('#')) {
|
||||||
|
window.location.hash = href;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,678 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Whatsapp - {{ name }}</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<script>
|
|
||||||
tailwind.config = {
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
whatsapp: {
|
|
||||||
light: '#e7ffdb',
|
|
||||||
DEFAULT: '#25D366',
|
|
||||||
dark: '#075E54',
|
|
||||||
chat: '#efeae2',
|
|
||||||
'chat-light': '#f0f2f5',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
scroll-behavior: smooth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list {
|
|
||||||
height: calc(100vh - 120px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list {
|
|
||||||
height: calc(100vh - 90px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
|
|
||||||
.chat-list,
|
|
||||||
.message-list {
|
|
||||||
height: calc(100vh - 108px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 20;
|
|
||||||
border-bottom: 2px solid #e3e6e7;
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bolder;
|
|
||||||
background-color: white;
|
|
||||||
padding: 20px 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
margin-top: 10px;
|
|
||||||
border-top: 2px solid #e3e6e7;
|
|
||||||
padding: 20px 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
width: 430px;
|
|
||||||
margin: auto;
|
|
||||||
z-index: 10;
|
|
||||||
font-size: 15px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
video,
|
|
||||||
audio {
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.reply {
|
|
||||||
font-size: 13px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:target::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
height: 115px;
|
|
||||||
margin-top: -115px;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:target {
|
|
||||||
animation: 3s highlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
color: #3892da;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pad-left-10 {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pad-right-10 {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply_link {
|
|
||||||
color: #168acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blue {
|
|
||||||
color: #70777a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticker {
|
|
||||||
max-width: 100px !important;
|
|
||||||
max-height: 100px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes highlight {
|
|
||||||
from {
|
|
||||||
background-color: rgba(37, 211, 102, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input.active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-box:active {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 4px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #8c8c8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator.read {
|
|
||||||
color: #34B7F1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.play-icon {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 8px solid white;
|
|
||||||
border-top: 5px solid transparent;
|
|
||||||
border-bottom: 5px solid transparent;
|
|
||||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.speaker-icon {
|
|
||||||
position: relative;
|
|
||||||
width: 8px;
|
|
||||||
height: 6px;
|
|
||||||
background: #666;
|
|
||||||
border-radius: 1px 0 0 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speaker-icon::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
right: -4px;
|
|
||||||
top: -1px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 4px solid #666;
|
|
||||||
border-top: 4px solid transparent;
|
|
||||||
border-bottom: 4px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speaker-icon::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
right: -8px;
|
|
||||||
top: -3px;
|
|
||||||
width: 8px;
|
|
||||||
height: 12px;
|
|
||||||
border: 2px solid #666;
|
|
||||||
border-left: none;
|
|
||||||
border-radius: 0 8px 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border: 2px solid #aebac1;
|
|
||||||
border-radius: 50%;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 2px;
|
|
||||||
height: 6px;
|
|
||||||
background: #aebac1;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
top: 12px;
|
|
||||||
left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-left {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid transparent;
|
|
||||||
border-right: 8px solid #aebac1;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-right {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid transparent;
|
|
||||||
border-left: 8px solid #aebac1;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 2px solid currentColor;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon::before {
|
|
||||||
content: 'i';
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
function search(event) {
|
|
||||||
keywords = document.getElementById("mainHeaderSearchInput").value;
|
|
||||||
hits = [];
|
|
||||||
document.querySelectorAll(".message-text").forEach(elem => {
|
|
||||||
if (elem.innerText.trim().includes(keywords)) {
|
|
||||||
hits.push(elem.parentElement.parentElement.id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log(hits);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<base href="{{ media_base }}" target="_blank">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<article class="h-screen bg-whatsapp-chat-light">
|
|
||||||
<div class="w-full flex flex-col">
|
|
||||||
<div class="p-3 bg-whatsapp-dark flex items-center justify-between border-l border-[#d1d7db]">
|
|
||||||
<div class="flex items-center">
|
|
||||||
{% if not no_avatar %}
|
|
||||||
<div class="w3-col m2 l2">
|
|
||||||
{% if their_avatar is not none %}
|
|
||||||
<a href="{{ their_avatar }}"><img src="{{ their_avatar_thumb or '' }}"
|
|
||||||
onerror="this.style.display='none'" class="w-10 h-10 rounded-full mr-3"
|
|
||||||
loading="lazy"></a>
|
|
||||||
{% else %}
|
|
||||||
<img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'"
|
|
||||||
class="w-10 h-10 rounded-full mr-3" loading="lazy">
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<h2 class="text-white font-medium">{{ headline }}</h2>
|
|
||||||
{% if status is not none %}<p class="text-[#8696a0] text-xs">{{ status }}</p>{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-4">
|
|
||||||
<!-- <button id="searchButton">
|
|
||||||
<span class="search-icon"></span>
|
|
||||||
</button> -->
|
|
||||||
<!-- <span class="arrow-left"></span> -->
|
|
||||||
{% if previous %}
|
|
||||||
<a href="./{{ previous }}" target="_self">
|
|
||||||
<span class="arrow-left"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if next %}
|
|
||||||
<a href="./{{ next }}" target="_self">
|
|
||||||
<span class="arrow-right"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<!-- Search Input Overlay -->
|
|
||||||
<div id="mainSearchInput"
|
|
||||||
class="search-input absolute article top-0 bg-whatsapp-dark p-3 flex items-center space-x-3">
|
|
||||||
<button id="closeMainSearch" class="text-[#aebac1]">
|
|
||||||
<span class="arrow-left"></span>
|
|
||||||
</button>
|
|
||||||
<input type="text" placeholder="Search..."
|
|
||||||
class="flex-1 bg-[#1f2c34] text-white rounded-lg px-3 py-1 focus:outline-none"
|
|
||||||
id="mainHeaderSearchInput" onkeyup="search(event)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 p-5 message-list">
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<!--Date-->
|
|
||||||
{% set last = {'last': 946688461.001} %}
|
|
||||||
{% for msg in msgs -%}
|
|
||||||
{% if determine_day(last.last, msg.timestamp) is not none %}
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="bg-[#e1f2fb] rounded-lg px-2 py-1 text-xs text-[#54656f]">
|
|
||||||
{{ determine_day(last.last, msg.timestamp) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if last.update({'last': msg.timestamp}) %}{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<!--Actual messages-->
|
|
||||||
{% if msg.from_me == true %}
|
|
||||||
<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">
|
|
||||||
<span class="info-icon text-[#8696a0] hover:text-[#54656f] cursor-pointer"></span>
|
|
||||||
<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">
|
|
||||||
{% if msg.reply is not none %}
|
|
||||||
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
|
||||||
<div
|
|
||||||
class="mb-2 p-1 bg-whatsapp-chat-light rounded border-l-4 border-whatsapp text-sm reply-box">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="flex-1 overflow-hidden">
|
|
||||||
<p class="text-whatsapp font-medium text-xs">Replying to</p>
|
|
||||||
<p class="text-[#111b21] text-xs truncate">
|
|
||||||
{% if msg.quoted_data is not none %}
|
|
||||||
"{{msg.quoted_data}}"
|
|
||||||
{% else %}
|
|
||||||
this message
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% set replied_msg = msgs | selectattr('key_id', 'equalto', msg.reply) | first %}
|
|
||||||
{% if replied_msg and replied_msg.media == true %}
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
{% if "image/" in replied_msg.mime %}
|
|
||||||
<img src="{{ replied_msg.thumb if replied_msg.thumb is not none else replied_msg.data }}"
|
|
||||||
class="w-8 h-8 rounded object-cover" loading="lazy" />
|
|
||||||
{% elif "video/" in replied_msg.mime %}
|
|
||||||
<div class="relative w-8 h-8 rounded overflow-hidden bg-gray-200">
|
|
||||||
<img src="{{ replied_msg.thumb if replied_msg.thumb is not none else replied_msg.data }}"
|
|
||||||
class="w-full h-full object-cover" loading="lazy" />
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="play-icon"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif "audio/" in replied_msg.mime %}
|
|
||||||
<div class="w-8 h-8 rounded bg-gray-200 flex items-center justify-center">
|
|
||||||
<div class="speaker-icon"></div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<p class="text-[#111b21] text-sm message-text">
|
|
||||||
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
|
||||||
<div class="flex justify-center mb-2">
|
|
||||||
<div class="bg-[#FFF3C5] rounded-lg px-3 py-2 text-sm text-[#856404] flex items-center">
|
|
||||||
{% if msg.safe %}
|
|
||||||
{{ msg.data | safe or 'Not supported WhatsApp internal message' }}
|
|
||||||
{% else %}
|
|
||||||
{{ msg.data or 'Not supported WhatsApp internal message' }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if msg.caption is not none %}
|
|
||||||
<p>{{ msg.caption | urlize(none, true, '_blank') }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if msg.media == false %}
|
|
||||||
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
|
||||||
{% else %}
|
|
||||||
{% if "image/" in msg.mime %}
|
|
||||||
<a href="{{ msg.data }}">
|
|
||||||
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' |
|
|
||||||
safe if msg.sticker }} loading="lazy" />
|
|
||||||
</a>
|
|
||||||
{% elif "audio/" in msg.mime %}
|
|
||||||
<audio controls="controls" autobuffer="autobuffer">
|
|
||||||
<source src="{{ msg.data }}" />
|
|
||||||
</audio>
|
|
||||||
{% elif "video/" in msg.mime %}
|
|
||||||
<video class="lazy" autobuffer {% if msg.message_type|int==13 or msg.message_type|int==11
|
|
||||||
%}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
|
||||||
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
|
||||||
</video>
|
|
||||||
{% elif "/" in msg.mime %}
|
|
||||||
The file cannot be displayed here, however it should be located at <a
|
|
||||||
href="./{{ msg.data }}">here</a>
|
|
||||||
{% else %}
|
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
|
||||||
{% endif %}
|
|
||||||
{% if msg.caption is not none %}
|
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<p class="text-[10px] text-[#667781] text-right mt-1">{{ msg.time }}
|
|
||||||
<span class="status-indicator{% if msg.read_timestamp %} read{% endif %}">
|
|
||||||
{% if msg.received_timestamp %}
|
|
||||||
✓✓
|
|
||||||
{% else %}
|
|
||||||
✓
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<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">
|
|
||||||
{% if msg.reply is not none %}
|
|
||||||
<a href="#{{msg.reply}}" target="_self" class="no-base">
|
|
||||||
<div
|
|
||||||
class="mb-2 p-1 bg-whatsapp-chat-light rounded border-l-4 border-whatsapp text-sm reply-box">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="flex-1 overflow-hidden">
|
|
||||||
<p class="text-whatsapp font-medium text-xs">Replying to</p>
|
|
||||||
<p class="text-[#808080] text-xs truncate">
|
|
||||||
{% if msg.quoted_data is not none %}
|
|
||||||
{{msg.quoted_data}}
|
|
||||||
{% else %}
|
|
||||||
this message
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% set replied_msg = msgs | selectattr('key_id', 'equalto', msg.reply) | first %}
|
|
||||||
{% if replied_msg and replied_msg.media == true %}
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
{% if "image/" in replied_msg.mime %}
|
|
||||||
<img src="{{ replied_msg.thumb if replied_msg.thumb is not none else replied_msg.data }}"
|
|
||||||
class="w-8 h-8 rounded object-cover" loading="lazy" />
|
|
||||||
{% elif "video/" in replied_msg.mime %}
|
|
||||||
<div class="relative w-8 h-8 rounded overflow-hidden bg-gray-200">
|
|
||||||
<img src="{{ replied_msg.thumb if replied_msg.thumb is not none else replied_msg.data }}"
|
|
||||||
class="w-full h-full object-cover" loading="lazy" />
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="play-icon"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif "audio/" in replied_msg.mime %}
|
|
||||||
<div class="w-8 h-8 rounded bg-gray-200 flex items-center justify-center">
|
|
||||||
<div class="speaker-icon"></div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<p class="text-[#111b21] text-sm">
|
|
||||||
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
|
||||||
<div class="flex justify-center mb-2">
|
|
||||||
<div class="bg-[#FFF3C5] rounded-lg px-3 py-2 text-sm text-[#856404] flex items-center">
|
|
||||||
{% if msg.safe %}
|
|
||||||
{{ msg.data | safe or 'Not supported WhatsApp internal message' }}
|
|
||||||
{% else %}
|
|
||||||
{{ msg.data or 'Not supported WhatsApp internal message' }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if msg.caption is not none %}
|
|
||||||
<p>{{ msg.caption | urlize(none, true, '_blank') }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if msg.media == false %}
|
|
||||||
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
|
||||||
{% else %}
|
|
||||||
{% if "image/" in msg.mime %}
|
|
||||||
<a href="{{ msg.data }}">
|
|
||||||
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' |
|
|
||||||
safe if msg.sticker }} loading="lazy" />
|
|
||||||
</a>
|
|
||||||
{% elif "audio/" in msg.mime %}
|
|
||||||
<audio controls="controls" autobuffer="autobuffer">
|
|
||||||
<source src="{{ msg.data }}" />
|
|
||||||
</audio>
|
|
||||||
{% elif "video/" in msg.mime %}
|
|
||||||
<video class="lazy" autobuffer {% if msg.message_type|int==13 or msg.message_type|int==11
|
|
||||||
%}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
|
||||||
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
|
||||||
</video>
|
|
||||||
{% elif "/" in msg.mime %}
|
|
||||||
The file cannot be displayed here, however it should be located at <a
|
|
||||||
href="./{{ msg.data }}">here</a>
|
|
||||||
{% else %}
|
|
||||||
{% filter escape %}{{ msg.data }}{% endfilter %}
|
|
||||||
{% endif %}
|
|
||||||
{% if msg.caption is not none %}
|
|
||||||
{{ msg.caption | urlize(none, true, '_blank') }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<div class="flex items-baseline text-[10px] text-[#667781] mt-1 gap-2">
|
|
||||||
<span class="flex-shrink-0">
|
|
||||||
{% if msg.sender is not none %}
|
|
||||||
{{ msg.sender }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
<span class="flex-grow min-w-[4px]"></span>
|
|
||||||
<span class="flex-shrink-0">{{ msg.time }}</span>
|
|
||||||
</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">
|
|
||||||
<span class="info-icon text-[#8696a0] hover:text-[#54656f] cursor-pointer"></span>
|
|
||||||
<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>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
{% if not next %}
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<div class="bg-[#e1f2fb] rounded-lg px-3 py-2 text-sm text-[#54656f]">
|
|
||||||
End of History
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<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>.
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
// Search functionality
|
|
||||||
const searchButton = document.getElementById('searchButton');
|
|
||||||
const mainSearchInput = document.getElementById('mainSearchInput');
|
|
||||||
const closeMainSearch = document.getElementById('closeMainSearch');
|
|
||||||
const mainHeaderSearchInput = document.getElementById('mainHeaderSearchInput');
|
|
||||||
|
|
||||||
// Function to show search input
|
|
||||||
const showSearch = () => {
|
|
||||||
mainSearchInput.classList.add('active');
|
|
||||||
mainHeaderSearchInput.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to hide search input
|
|
||||||
const hideSearch = () => {
|
|
||||||
mainSearchInput.classList.remove('active');
|
|
||||||
mainHeaderSearchInput.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
searchButton.addEventListener('click', showSearch);
|
|
||||||
closeMainSearch.addEventListener('click', hideSearch);
|
|
||||||
|
|
||||||
// Handle ESC key
|
|
||||||
document.addEventListener('keydown', (event) => {
|
|
||||||
if (event.key === 'Escape' && mainSearchInput.classList.contains('active')) {
|
|
||||||
hideSearch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
|
|
||||||
|
|
||||||
if ("IntersectionObserver" in window) {
|
|
||||||
var lazyVideoObserver = new IntersectionObserver(function (entries, observer) {
|
|
||||||
entries.forEach(function (video) {
|
|
||||||
if (video.isIntersecting) {
|
|
||||||
for (var source in video.target.children) {
|
|
||||||
var videoSource = video.target.children[source];
|
|
||||||
if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
|
|
||||||
videoSource.src = videoSource.dataset.src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video.target.load();
|
|
||||||
video.target.classList.remove("lazy");
|
|
||||||
lazyVideoObserver.unobserve(video.target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
lazyVideos.forEach(function (lazyVideo) {
|
|
||||||
lazyVideoObserver.observe(lazyVideo);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
// Prevent the <base> tag from affecting links with the class "no-base"
|
|
||||||
document.querySelectorAll('.no-base').forEach(link => {
|
|
||||||
link.addEventListener('click', function (event) {
|
|
||||||
const href = this.getAttribute('href');
|
|
||||||
if (href.startsWith('#')) {
|
|
||||||
window.location.hash = href;
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
329
Whatsapp_Chat_Exporter/whatsapp_old.html
Normal file
329
Whatsapp_Chat_Exporter/whatsapp_old.html
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Whatsapp - {{ name }}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="{{w3css}}">
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
font-size: 12px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 20;
|
||||||
|
border-bottom: 2px solid #e3e6e7;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bolder;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px 0 20px 0;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
border-top: 2px solid #e3e6e7;
|
||||||
|
padding: 20px 0 20px 0;
|
||||||
|
}
|
||||||
|
article {
|
||||||
|
width:500px;
|
||||||
|
margin:100px auto;
|
||||||
|
z-index:10;
|
||||||
|
font-size: 15px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
img, video {
|
||||||
|
max-width:100%;
|
||||||
|
}
|
||||||
|
div.reply{
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
div:target::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 115px;
|
||||||
|
margin-top: -115px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
div:target {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
animation: border-blink 0.5s steps(1) 5;
|
||||||
|
border-color: rgba(0,0,0,0)
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@keyframes border-blink {
|
||||||
|
0% {
|
||||||
|
border-color: #2196F3;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
border-radius:50%;
|
||||||
|
overflow:hidden;
|
||||||
|
max-width: 64px;
|
||||||
|
max-height: 64px;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
color: #3892da;
|
||||||
|
}
|
||||||
|
.pad-left-10 {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.pad-right-10 {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.reply_link {
|
||||||
|
color: #168acc;
|
||||||
|
}
|
||||||
|
.blue {
|
||||||
|
color: #70777a;
|
||||||
|
}
|
||||||
|
.sticker {
|
||||||
|
max-width: 100px !important;
|
||||||
|
max-height: 100px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<base href="{{ media_base }}" target="_blank">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="w3-center w3-top">
|
||||||
|
{{ headline }}
|
||||||
|
{% if status is not none %}
|
||||||
|
<br>
|
||||||
|
<span class="w3-small">{{ status }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
<article class="w3-container">
|
||||||
|
<div class="table">
|
||||||
|
{% set last = {'last': 946688461.001} %}
|
||||||
|
{% for msg in msgs -%}
|
||||||
|
<div class="w3-row w3-padding-small w3-margin-bottom" id="{{ msg.key_id }}">
|
||||||
|
{% if determine_day(last.last, msg.timestamp) is not none %}
|
||||||
|
<div class="w3-center w3-padding-16 blue">{{ determine_day(last.last, msg.timestamp) }}</div>
|
||||||
|
{% if last.update({'last': msg.timestamp}) %}{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.from_me == true %}
|
||||||
|
<div class="w3-row">
|
||||||
|
<div class="w3-left blue">{{ msg.time }}</div>
|
||||||
|
<div class="name w3-right-align pad-left-10">You</div>
|
||||||
|
</div>
|
||||||
|
<div class="w3-row">
|
||||||
|
{% if not no_avatar and my_avatar is not none %}
|
||||||
|
<div class="w3-col m10 l10">
|
||||||
|
{% else %}
|
||||||
|
<div class="w3-col m12 l12">
|
||||||
|
{% endif %}
|
||||||
|
<div class="w3-right-align">
|
||||||
|
{% if msg.reply is not none %}
|
||||||
|
<div class="reply">
|
||||||
|
<span class="blue">Replying to </span>
|
||||||
|
<a href="#{{msg.reply}}" target="_self" class="reply_link no-base">
|
||||||
|
{% if msg.quoted_data is not none %}
|
||||||
|
"{{msg.quoted_data}}"
|
||||||
|
{% else %}
|
||||||
|
this message
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
||||||
|
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
||||||
|
{% if msg.safe %}
|
||||||
|
<p>{{ msg.data | safe or 'Not supported WhatsApp internal message' }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ msg.data or 'Not supported WhatsApp internal message' }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
<div class="w3-container">
|
||||||
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if msg.media == false %}
|
||||||
|
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
||||||
|
{% else %}
|
||||||
|
{% if "image/" in msg.mime %}
|
||||||
|
<a href="{{ msg.data }}">
|
||||||
|
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
||||||
|
</a>
|
||||||
|
{% elif "audio/" in msg.mime %}
|
||||||
|
<audio controls="controls" autobuffer="autobuffer">
|
||||||
|
<source src="{{ msg.data }}" />
|
||||||
|
</audio>
|
||||||
|
{% elif "video/" in msg.mime %}
|
||||||
|
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
||||||
|
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
||||||
|
</video>
|
||||||
|
{% elif "/" in msg.mime %}
|
||||||
|
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
||||||
|
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
<div class="w3-container">
|
||||||
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if not no_avatar and my_avatar is not none %}
|
||||||
|
<div class="w3-col m2 l2 pad-left-10">
|
||||||
|
<a href="{{ my_avatar }}">
|
||||||
|
<img src="{{ my_avatar }}" onerror="this.style.display='none'" class="avatar" loading="lazy">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="w3-row">
|
||||||
|
<div class="w3-left pad-right-10 name">
|
||||||
|
{% if msg.sender is not none %}
|
||||||
|
{{ msg.sender }}
|
||||||
|
{% else %}
|
||||||
|
{{ name }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="w3-right-align blue">{{ msg.time }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="w3-row">
|
||||||
|
{% if not no_avatar %}
|
||||||
|
<div class="w3-col m2 l2">
|
||||||
|
{% if their_avatar is not none %}
|
||||||
|
<a href="{{ their_avatar }}"><img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="avatar" loading="lazy"></a>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ their_avatar_thumb or '' }}" onerror="this.style.display='none'" class="avatar" loading="lazy">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="w3-col m10 l10">
|
||||||
|
{% else %}
|
||||||
|
<div class="w3-col m12 l12">
|
||||||
|
{% endif %}
|
||||||
|
<div class="w3-left-align">
|
||||||
|
{% if msg.reply is not none %}
|
||||||
|
<div class="reply">
|
||||||
|
<span class="blue">Replying to </span>
|
||||||
|
<a href="#{{msg.reply}}" target="_self" class="reply_link no-base">
|
||||||
|
{% if msg.quoted_data is not none %}
|
||||||
|
"{{msg.quoted_data}}"
|
||||||
|
{% else %}
|
||||||
|
this message
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.meta == true or msg.media == false and msg.data is none %}
|
||||||
|
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
||||||
|
{% if msg.safe %}
|
||||||
|
<p>{{ msg.data | safe or 'Not supported WhatsApp internal message' }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ msg.data or 'Not supported WhatsApp internal message' }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
<div class="w3-container">
|
||||||
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if msg.media == false %}
|
||||||
|
{{ msg.data | sanitize_except() | urlize(none, true, '_blank') }}
|
||||||
|
{% else %}
|
||||||
|
{% if "image/" in msg.mime %}
|
||||||
|
<a href="{{ msg.data }}">
|
||||||
|
<img src="{{ msg.thumb if msg.thumb is not none else msg.data }}" {{ 'class="sticker"' | safe if msg.sticker }} loading="lazy"/>
|
||||||
|
</a>
|
||||||
|
{% elif "audio/" in msg.mime %}
|
||||||
|
<audio controls="controls" autobuffer="autobuffer">
|
||||||
|
<source src="{{ msg.data }}" />
|
||||||
|
</audio>
|
||||||
|
{% elif "video/" in msg.mime %}
|
||||||
|
<video class="lazy" autobuffer {% if msg.message_type|int == 13 or msg.message_type|int == 11 %}autoplay muted loop playsinline{%else%}controls{% endif %}>
|
||||||
|
<source type="{{ msg.mime }}" data-src="{{ msg.data }}" />
|
||||||
|
</video>
|
||||||
|
{% elif "/" in msg.mime %}
|
||||||
|
<div class="w3-panel w3-border-blue w3-pale-blue w3-rightbar w3-leftbar w3-threequarter w3-center">
|
||||||
|
<p>The file cannot be displayed here, however it should be located at <a href="./{{ msg.data }}">here</a></p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% filter escape %}{{ msg.data }}{% endfilter %}
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.caption is not none %}
|
||||||
|
<div class="w3-container">
|
||||||
|
{{ msg.caption | urlize(none, true, '_blank') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<footer class="w3-center">
|
||||||
|
<h2>
|
||||||
|
{% if previous %}
|
||||||
|
<a href="./{{ previous }}" target="_self">Previous</a>
|
||||||
|
{% endif %}
|
||||||
|
<h2>
|
||||||
|
{% if next %}
|
||||||
|
<a href="./{{ next }}" target="_self">Next</a>
|
||||||
|
{% else %}
|
||||||
|
End of History
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
<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>.
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
|
||||||
|
|
||||||
|
if ("IntersectionObserver" in window) {
|
||||||
|
var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
|
||||||
|
entries.forEach(function(video) {
|
||||||
|
if (video.isIntersecting) {
|
||||||
|
for (var source in video.target.children) {
|
||||||
|
var videoSource = video.target.children[source];
|
||||||
|
if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
|
||||||
|
videoSource.src = videoSource.dataset.src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video.target.load();
|
||||||
|
video.target.classList.remove("lazy");
|
||||||
|
lazyVideoObserver.unobserve(video.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lazyVideos.forEach(function(lazyVideo) {
|
||||||
|
lazyVideoObserver.observe(lazyVideo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Prevent the <base> tag from affecting links with the class "no-base"
|
||||||
|
document.querySelectorAll('.no-base').forEach(link => {
|
||||||
|
link.addEventListener('click', function(event) {
|
||||||
|
const href = this.getAttribute('href');
|
||||||
|
if (href.startsWith('#')) {
|
||||||
|
window.location.hash = href;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user