LPSG Video Unlocker

Automatically unlocks and enhances your LPSG browsing experience

// ==UserScript==
// @name         LPSG Video Unlocker
// @namespace    MBing & CurlyWurly
// @version      3.8
// @description  Automatically unlocks and enhances your LPSG browsing experience
// @author       MBing & CurlyWurly
// @match        https://www.lpsg.com/*
// @icon         data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pjxzdmcgdmlld0JveD0iMCAwIDI1NiAyNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNTYiIHdpZHRoPSIyNTYiLz48cGF0aCBkPSJNOTMuMiwxMjIuOEE3MC4zLDcwLjMsMCwwLDEsODgsOTZhNzIsNzIsMCwxLDEsNzIsNzIsNzAuMyw3MC4zLDAsMCwxLTI2LjgtNS4yaDBMMTIwLDE3Nkg5NnYyNEg3MnYyNEgzMlYxODRsNjEuMi02MS4yWiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZENzAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMTYiLz48Y2lyY2xlIGN4PSIxODAiIGN5PSI3NiIgcj0iMTIiIGZpbGw9IiNGRkQ3MDAiLz48L3N2Zz4=
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const volume = 0.1;
    const formats = ['mp4', 'm4v', 'mov'];

    var easterEggPoster = document.getElementsByClassName("video-easter-egg-poster");
    var videoDiv = [];
    var imageUrl;
    var newDiv;

    // Create video elements with all format sources
    for (var i = easterEggPoster.length - 1; i > -1; i--) {
        imageUrl = easterEggPoster[i].children[0].src;
        let sourceElements = formats.map(format => {
            let videoUrl = imageUrl.replace("attachments/posters", "video")
                .replace("/lsvideo/thumbnails", "lsvideo/videos")
                .replace(".jpg", `.${format}`);

            // Set correct MIME type for each format
            let mimeType;
            switch (format) {
                case 'mp4':
                    mimeType = 'video/mp4';
                    break;
                case 'm4v':
                    mimeType = 'video/x-m4v';
                    break;
                case 'mov':
                    mimeType = 'video/quicktime';
                    break;
            }

            return `<source data-src="${videoUrl}" src="${videoUrl}" type="${mimeType}">`;
        }).join('');

        videoDiv[i] = `<video onloadstart="this.volume=${volume}" 
            style="width: 100%; height: auto; display: block;" 
            playsinline
            controls="" 
            data-xf-init="video-init" 
            data-poster="${imageUrl}" 
            class="message-cell--main-video" 
            poster="${imageUrl}">
            ${sourceElements}
            <div class="bbMediaWrapper-fallback">Your browser is not able to display this video.</div>
        </video>`;

        newDiv = document.createElement("div");
        newDiv.setAttribute("class", "newVideoDiv");
        newDiv.innerHTML = videoDiv[i];
        easterEggPoster[i].parentElement.parentElement.append(newDiv);
    }

    // Remove original elements
    for (i = easterEggPoster.length - 1; i > -1; i--) {
        easterEggPoster[i].parentElement.parentElement.removeChild(easterEggPoster[i].parentElement);
    }

    // Remove blockers and overlays
    ['video-easter-egg-blocker', 'video-easter-egg-overlay'].forEach(className => {
        var elements = document.getElementsByClassName(className);
        for (var j = elements.length - 1; j > -1; j--) {
            elements[j].parentElement.removeChild(elements[j]);
        }
    });

    // Set volume for all video players
    var allVideoPlayers = document.getElementsByTagName('video');
    for (i = allVideoPlayers.length - 1; i > -1; i--) {
        allVideoPlayers[i].volume = volume;
    }

    // Enhanced image loading
    function unlockAllImages() {
        document.querySelectorAll('img[loading="lazy"]').forEach(img => {
            img.loading = 'eager';
            if (img.dataset.src) {
                img.src = img.dataset.src;
            }
        });
    }

    // Add this function to create the media gallery button and functionality
    function createMediaButton() {
        // Create button
        const mediaBtn = document.createElement("a");
        mediaBtn.innerHTML = '<span class="button-text"><i class="fa--xf fas fa-images" aria-hidden="true"></i><span class="u-srOnly">Media</span></span>';
        mediaBtn.className = "button--scroll ripple-JsOnly button";
        mediaBtn.style.cssText = "margin: 2px;";

        mediaBtn.addEventListener('click', () => {
            // Create gallery container
            const gallery = document.createElement("div");
            gallery.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0,0,0,0.9);
            z-index: 9999;
            overflow-y: auto;
            padding: 20px;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
        `;

            // Collect all media in order of appearance
            // Collect all media in order of appearance
            const media = [...document.querySelectorAll('.message-cell--main img, .message-cell--main video')]
                .filter(element => {
                    // Only include images with '/attachments/' in URL and all videos
                    return element.tagName.toLowerCase() === 'video' ||
                        (element.tagName.toLowerCase() === 'img' && element.src.includes('/attachments/'));
                });

            // Add media to gallery in original order
            media.forEach(element => {
                const clone = element.cloneNode(true);
                clone.style.width = '100%';
                clone.style.height = 'auto';
                clone.style.maxHeight = '500px';
                clone.style.objectFit = 'contain';

                // If it's an image, wrap it in a clickable link to the full version
                if (element.tagName.toLowerCase() === 'img') {
                    // Find the parent anchor tag that contains the full image URL
                    const parentAnchor = element.closest('a[href*="/attachments/"]');
                    if (parentAnchor) {
                        const wrapper = document.createElement('a');
                        wrapper.href = parentAnchor.href;
                        wrapper.target = '_blank';
                        wrapper.style.cursor = 'pointer';
                        wrapper.appendChild(clone);
                        gallery.appendChild(wrapper);
                    } else {
                        gallery.appendChild(clone);
                    }
                } else {
                    gallery.appendChild(clone);
                }
            });



            // Add close button
            const closeBtn = document.createElement("button");
            closeBtn.innerHTML = "✖️";
            closeBtn.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            padding: 10px;
            background: white;
            border: none;
            border-radius: 50%;
            cursor: pointer;
            z-index: 10000;
        `;
            closeBtn.onclick = () => document.body.removeChild(gallery);

            gallery.appendChild(closeBtn);
            document.body.appendChild(gallery);
        });

        // Add button to scroll buttons container
        const scrollButtons = document.querySelector('.u-scrollButtons');
        if (scrollButtons) {
            scrollButtons.appendChild(mediaBtn);
        }
    }

    // Run image loading immediately and after a delay to catch dynamic content
    unlockAllImages();
    setTimeout(unlockAllImages, 1000);
    createMediaButton();
})();