export_template.html (13830B) - raw
1 <style> 2 :root { 3 --Snap-sigIconPrimary: #dedede; 4 --Snap-sigIconSecondary: #999; 5 --Snap-sigIconTertiary: #616161; 6 --Snap-sigIconNegative: #f23c57; 7 --Snap-sigTextPrimary: #dedede; 8 --Snap-sigTextPrimaryInverse: #000; 9 --Snap-sigTextSecondary: #999; 10 --Snap-sigTextTertiary: #616161; 11 --Snap-sigTextPlayer: #fff; 12 --Snap-sigTextNegative: #f23c57; 13 --Snap-sigColorBackgroundBorder: rgba(255, 255, 255, 0.1); 14 --Snap-sigBackgroundPrimary: #121212; 15 --Snap-sigBackgroundPrimaryInverse: #fff; 16 --Snap-sigBackgroundSecondary: #1e1e1e; 17 --Snap-sigBackgroundSecondaryHover: #2b2b2b; 18 --Snap-sigBackgroundFeedHover: rgba(255, 255, 255, 0.1); 19 --Snap-sigBackgroundMessageHover: #292929; 20 --Snap-sigBackgroundMessageSaved: #333232; 21 --Snap-sigBackgroundMessageSavedHover: #3a3a3a; 22 --Snap-sigMediaControlContainerBackground: rgba(255, 255, 255, 0.1); 23 --Snap-sigStartupFooterBackground: rgba(0, 0, 0, 0.05); 24 --Snap-sigButtonPrimary: #0fadff; 25 --Snap-sigButtonPrimaryHover: #42bfff; 26 --Snap-sigButtonSecondary: #2b2b2b; 27 --Snap-sigButtonSecondaryHover: #424242; 28 --Snap-sigButtonSecondaryActive: #5c5c5c; 29 --Snap-sigButtonTertiary: #4e565f; 30 --Snap-sigButtonQuaternary: #fff; 31 --Snap-sigButtonInactive: #1e1e1e; 32 --Snap-sigButtonNegative: #e1143d; 33 --Snap-sigButtonOnPrimary: #fff; 34 --Snap-sigButtonOnSecondary: #dedede; 35 --Snap-sigButtonOnTertiary: #fff; 36 --Snap-sigButtonOnQuaternary: #1e1e1e; 37 --Snap-sigButtonOnInactive: rgba(255, 255, 255, 0.3); 38 --Snap-sigButtonOnNegative: #fff; 39 --Snap-sigMain: #121212; 40 --Snap-sigSubscreen: #121212; 41 --Snap-sigOverlay: rgba(0, 0, 0, 0.4); 42 --Snap-sigOverlayHover: rgba(0, 0, 0, 0.35); 43 --Snap-sigSurface: #1e1e1e; 44 --Snap-sigSurfaceRGB: 30, 30, 30; 45 --Snap-sigSurfaceDown: #212121; 46 --Snap-sigAboveSurface: #292929; 47 --Snap-sigObject: rgba(255, 255, 255, 0.1); 48 --Snap-sigObjectDown: rgba(255, 255, 255, 0.19); 49 --Snap-sigConversationBoxBackground: rgba(255, 255, 255, 0.25); 50 --Snap-sigDivider: rgba(255, 255, 255, 0.1); 51 --Snap-sigDividerLight: rgba(255, 255, 255, 0.2); 52 --Snap-sigPlaceholder: #1e1e1e; 53 --Snap-sigDisabled: rgba(255, 255, 255, 0.1); 54 --Snap-sigCallTileHighlight: rgba(255, 255, 255, 0.8); 55 --Snap-sigChat: #0fadff; 56 --Snap-sigSnapWithoutSound: #f23c57; 57 --Snap-sigSnapWithSound: #a05dcd; 58 --Snap-sigChatSurfaceCalling: #39ca8e; 59 --Snap-sigChatSurfaceCallingDisabled: #105e3d; 60 --Snap-sigChatPending: #767676; 61 --Snap-sigChatPendingHover: #8f8f8f; 62 --Snap-sigChatIcon: #0fadff; 63 --Snap-sigChatIconCaret: #f8616d; 64 --Snap-sigChatShadowOne: 0 0 17px rgba(33, 33, 33, 0.07), 0 0 22px rgba(0, 0, 0, 0.06), 0 0 8px rgba(84, 84, 84, 0.1); 65 --Snap-selectedMiddleColorGradient: rgba(4, 4, 4, 0.1); 66 --Snap-selectedRightColorGradient: rgba(4, 4, 4, 0); 67 --Border: 1px solid var(--Snap-sigColorBackgroundBorder); 68 } 69 70 body { 71 font-family: 'Avenir Next', sans-serif; 72 color: var(--Snap-sigTextPrimary); 73 background-color: var(--Snap-sigBackgroundPrimary); 74 margin: 0; 75 padding: 0; 76 display: flex; 77 flex-direction: column; 78 align-items: center; 79 justify-content: flex-start; 80 } 81 82 header { 83 width: 100%; 84 padding: 10px 0px; 85 display: flex; 86 flex-direction: column; 87 align-items: center; 88 justify-content: flex-start; 89 } 90 91 header .title { 92 background-color: var(--Snap-sigButtonSecondary); 93 height: 40px; 94 font-weight: 600; 95 padding-inline-end: 16px; 96 border-radius: 999px; 97 line-height: 40px; 98 padding: 0 20px; 99 } 100 101 main { 102 background-color: var(--Snap-sigBackgroundSecondary); 103 border: var(--Border); 104 border-radius: 12px; 105 width: calc(100% - 30px); 106 display: flex; 107 flex-direction: column; 108 } 109 110 main>.message { 111 display: flex; 112 flex-direction: column; 113 align-items: stretch; 114 justify-content: flex-start; 115 flex-wrap: nowrap; 116 margin: 5px 15px; 117 } 118 119 main>.message .header { 120 width: 100%; 121 display: flex; 122 vertical-align: top; 123 align-self: flex-start; 124 flex-direction: row; 125 flex-wrap: nowrap; 126 justify-content: space-between; 127 align-items: center; 128 } 129 130 main>.message:nth-child(2n) .username { 131 color: #dcedc1; 132 } 133 134 main>.message:nth-child(2n + 1) .username { 135 color: #ffd3b6; 136 } 137 138 main>.message .username { 139 font-weight: bold; 140 } 141 142 main>.message .time { 143 color: var(--Snap-sigTextSecondary); 144 font-size: 12px; 145 font-weight: 600; 146 } 147 148 main>.message:nth-child(2n) .content { 149 border-color: #dcedc1; 150 } 151 152 main>.message:nth-child(2n + 1) .content { 153 border-color: #ffd3b6; 154 } 155 156 main>.message .content { 157 background-color: var(--Snap-sigBackgroundMessageSaved); 158 border-left: 3px solid; 159 border-radius: 3px; 160 margin-top: 4px; 161 padding-left: 4px; 162 padding: 3px 0 3px 6px; 163 } 164 165 main>.message .content div:has(.chat_media:not(audio):not(.overlay_media)) { 166 display: inline-block; 167 resize: horizontal; 168 overflow: hidden; 169 line-height: 0; 170 height: auto; 171 width: 300px; 172 } 173 174 main>.message .chat_media:not(audio):not(.overlay_media) { 175 width: 100%; 176 height: auto; 177 } 178 179 @-moz-document url-prefix() { 180 main>.message .content div { 181 display: inline-block; 182 resize: horizontal; 183 overflow: hidden; 184 line-height: 0; 185 height: auto; 186 width: 300px; 187 } 188 189 main>.message .chat_media:not(.overlay_media) { 190 width: 100%; 191 height: auto; 192 } 193 } 194 195 196 main>.message .overlay_media { 197 width: inherit; 198 height: inherit; 199 position: absolute; 200 pointer-events: none; 201 } 202 203 main>.message .red_snap_svg { 204 color: var(--Snap-sigSnapWithoutSound); 205 } 206 </style> 207 <body> 208 <header> 209 <div class="title"></div> 210 <div> 211 <label> 212 <input type="checkbox" class="sort_by_date" onchange="makeMain();"> Sort by date 213 </label> 214 </div> 215 </header> 216 217 <main></main> 218 219 <div style="display: none;"> 220 <svg class="red_snap_svg" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 221 <rect x="4" y="5" width="10.5" height="10.5" rx="1.808" stroke="currentColor" stroke-width="1.5"></rect> 222 </svg> 223 </div> 224 225 <script> 226 function base64decode(data) { 227 return new Uint8Array(atob(data).split('').map(c => c.charCodeAt(0))) 228 } 229 230 const conversationData = JSON.parse(new TextDecoder().decode(new Uint8Array(inflate(base64decode(document.querySelector(".exported_content").innerHTML))))) 231 const participants = Object.values(conversationData.participants) 232 233 function makeHeader() { 234 const conversationTitle = conversationData.conversationName != null ? conversationData.conversationName : "DM with " + Object.values(participants).map(user => user.username).join(", ") 235 document.querySelector("header > .title").textContent = conversationTitle 236 document.title = conversationTitle 237 } 238 239 function decodeMedia(element) { 240 try { 241 const decodedData = new Uint8Array( 242 inflate( 243 base64decode( 244 element.innerHTML.substring(5, element.innerHTML.length - 4) 245 ) 246 ) 247 ) 248 return URL.createObjectURL(new Blob([decodedData])) 249 } catch (e) { 250 return null 251 } 252 } 253 254 function makeMain() { 255 document.querySelector('main').innerHTML = "" 256 const messageTemplate = document.querySelector("#message_template") 257 let messageList = Object.values(conversationData.messages) 258 259 if (document.querySelector(".sort_by_date").checked) { 260 messageList = messageList.reverse() 261 } 262 263 messageList.forEach(message => { 264 const messageObject = document.createElement("div") 265 messageObject.classList.add("message") 266 267 messageObject.appendChild(((headerElement) => { 268 headerElement.classList.add("header") 269 270 headerElement.appendChild(((elem) => { 271 elem.classList.add("username") 272 const participant = participants[message.senderId] 273 elem.innerHTML = (participant == null) ? "Unknown user" : participant.username 274 return elem 275 })(document.createElement("div"))) 276 277 278 headerElement.appendChild(((elem) => { 279 elem.classList.add("time") 280 elem.innerHTML = new Date(message.createdTimestamp).toUTCString() 281 return elem 282 })(document.createElement("div"))) 283 284 return headerElement 285 })(document.createElement("div"))) 286 287 messageObject.appendChild(((messageContainer) => { 288 messageContainer.classList.add("content") 289 290 const observers = [] 291 292 function loadContent() { 293 if (!message.serializedContent) { 294 let messageData = "" 295 switch (message.type) { 296 case "SNAP": 297 messageContainer.appendChild(document.querySelector('.red_snap_svg').cloneNode(true)) 298 messageData += "Snap" 299 break 300 default: 301 messageData += message.type 302 } 303 messageContainer.innerHTML = messageData 304 messageContainer.onclick = () => { 305 messageContainer.prepend(document.createElement("br")) 306 observers.forEach(f => f()) 307 } 308 } else { 309 messageContainer.innerHTML = message.serializedContent 310 } 311 } 312 313 loadContent() 314 315 if (message.attachments && message.attachments.length > 0) { 316 message.attachments.reverse().forEach((attachment, index) => { 317 const mediaKey = attachment.key.replace(/(=)/g, "") 318 319 observers.push(() => { 320 messageContainer.onclick = () => {} 321 const originalMedia = document.querySelector('.media-ORIGINAL_' + mediaKey) 322 if (!originalMedia) { 323 return 324 } 325 326 const originalMediaUrl = decodeMedia(originalMedia) 327 328 const mediaContainer = document.createElement("div") 329 messageContainer.prepend(mediaContainer) 330 331 const imageTag = document.createElement("img") 332 imageTag.src = originalMediaUrl 333 imageTag.classList.add("chat_media") 334 mediaContainer.appendChild(imageTag) 335 336 imageTag.onerror = () => { 337 mediaContainer.removeChild(imageTag) 338 const mediaTag = document.createElement(message.type === "NOTE" ? "audio" : "video") 339 mediaTag.classList.add("chat_media") 340 mediaTag.src = originalMediaUrl 341 mediaTag.preload = "metadata" 342 mediaTag.controls = true 343 mediaContainer.appendChild(mediaTag) 344 } 345 346 const overlay = document.querySelector('.media-OVERLAY_' + mediaKey) 347 if (!overlay) { 348 return 349 } 350 351 const overlayImage = document.createElement("img") 352 overlayImage.src = decodeMedia(overlay) 353 overlayImage.classList.add("chat_media") 354 overlayImage.classList.add("overlay_media") 355 mediaContainer.appendChild(overlayImage) 356 }) 357 }) 358 359 let fetched = false 360 361 new IntersectionObserver(entries => { 362 if (!fetched && entries[0].isIntersecting === true) { 363 fetched = true 364 loadContent() 365 messageContainer.prepend(document.createElement("br")) 366 observers.forEach(c => { 367 try { 368 c() 369 } catch (e) { 370 console.log(e) 371 } 372 }) 373 } 374 }).observe(messageContainer) 375 } 376 377 return messageContainer 378 })(document.createElement("div"))) 379 380 document.querySelector('main').appendChild(messageObject) 381 }) 382 } 383 384 makeHeader() 385 makeMain() 386 </script> 387 </body>