WhatsApp Hide Chat List

Hide or show the WhatsApp Web chat list based on user interaction. Includes features for resizing and locking Chat list visibility.

// ==UserScript==
// @name        WhatsApp Hide Chat List
// @namespace   imxitiz's-Script
// @version     3.0.2
// @grant       none
// @license     GNU GPLv3
// @author      imxitiz
// @contributor imxitiz
// @match       https://web.whatsapp.com/
// @description Hide or show the WhatsApp Web chat list based on user interaction. Includes features for resizing and locking Chat list visibility.
// ==/UserScript==

(function () {
    ("use strict");
    let hasInitialized = false;
    const hideThreshold = 20; // Threshold in pixels to determine if sidebar should be hidden
    let eventParent;
    let isResizing = false;
    let userDefinedFlexBasis =
        parseFloat(getLocalStorageItem("userDefinedFlexBasis")) || 30;
    let userResizedOnce =
        getSessionStorageItem("userResizedOnce") === "true" || false;
    let active = parseInt(getSessionStorageItem("active")) || 0;
    // active states:
    // 0 - Normal behavior: sidebar hidden on hover
    // 1 - Always visible
    // 2 - Always hidden(lock)
    // 3 - Always hidden(hiddent but not locked)
    let blurEffect = getSessionStorageItem("blurEffect") === "true" || false;
    let lockPosition = JSON.parse(getSessionStorageItem("lockPosition")) || {
        x: 0,
        y: 0,
    };
    const clickThreshold = 80; // Threshold in pixels for precise unlocking
    let wrongLockedPlaceAttempt = 0;

    const sidebarElementSelector = "div._aigw:nth-child(4)";
    const inboxSwitcherElementSelector = "header";

    // Helper functions for storage
    function setSessionStorageItem(key, value) {
        sessionStorage.setItem(key, value);
    }

    function getSessionStorageItem(key) {
        return sessionStorage.getItem(key);
    }

    function setLocalStorageItem(key, value) {
        localStorage.setItem(key, value);
    }

    function getLocalStorageItem(key) {
        return localStorage.getItem(key);
    }

    // Create and append the resize handle to the sidebar
    function createResizeHandle() {
        try {
            const sidebar = document.querySelector(sidebarElementSelector);
            if (!sidebar) {
                throw new Error("Sidebar element not found");
            }

            // Create resize handle only if it doesn't already exist
            if (!document.getElementById("resize-handle")) {
                const resizeHandle = document.createElement("div");
                resizeHandle.id = "resize-handle";
                resizeHandle.style.width = "10px";
                resizeHandle.style.height = "100%";
                resizeHandle.style.position = "absolute";
                resizeHandle.style.top = "0";
                resizeHandle.style.right = "0";
                resizeHandle.style.cursor = "ew-resize";
                resizeHandle.style.backgroundColor = userResizedOnce
                    ? "transparent"
                    : "red";
                resizeHandle.style.zIndex = "1000";
                sidebar.appendChild(resizeHandle);

                // Add event listeners for resizing
                resizeHandle.addEventListener("mousedown", function () {
                    isResizing = true;
                    document.addEventListener("mousemove", resizeChatList);
                    document.addEventListener("mouseup", stopResize);
                });
            }
        } catch (error) {
            console.error("Error creating resize handle: ", error);
            setTimeout(createResizeHandle, 1000); // Retry after 1 second if there's an error
        }
    }

    function changeVisibility(sidebar, show) {
        if (show) {
            sidebar.style.maxWidth = `${userDefinedFlexBasis}%`;
            sidebar.style.minWidth = `${userDefinedFlexBasis}`;
            sidebar.style.flex = `0 0 ${userDefinedFlexBasis}%`;
        } else {
            sidebar.style.width = "0";
            sidebar.style.maxWidth = "0";
            sidebar.style.minWidth = "0";
            sidebar.style.flex = "0 0 0";
        }
    }

    function initialize() {
        const sidebar = document.querySelector(sidebarElementSelector);
        if (sidebar) {
            if (!hasInitialized) {
                if (sidebar.style.maxWidth != "0px") {
                    changeVisibility(sidebar, active === 1);
                    createBlurEffect(blurEffect);
                    setTimeout(initialize, 1000);
                }
                if (sidebar.style.maxWidth == "0px") {
                    setTimeout(() => {
                        changeVisibility(sidebar, active === 1);
                        createBlurEffect(blurEffect);
                    }, 2000);
                }
            }
        } else {
            setTimeout(initialize, 1000);
        }
    }

    // Update sidebar visibility based on user interactions and settings
    function updateSidebarVisibility() {
        const sidebar = document.querySelector(sidebarElementSelector);
        const inboxSwitcher = document.querySelector(
            inboxSwitcherElementSelector
        );

        if (sidebar && inboxSwitcher) {
            if (!hasInitialized) {
                sidebar.style.display = "flex";
                sidebar.style.flex = `0 0 ${userDefinedFlexBasis}%`;
                sidebar.style.maxWidth = "100%";
                sidebar.style.width = "0%";
                sidebar.style.transition = "width .5s ease-out";
                sidebar.style.position = "relative";
                sidebar.style.overflow = "hidden";
                hasInitialized = true;

                createResizeHandle(); // Create resize handle on initial setup
            }
            const isMouseOverSidebar =
                isMouseOver(sidebar) || isMouseOver(inboxSwitcher);

            if (active === 1) {
                // Always show the sidebar
                changeVisibility(sidebar, true);
            } else if (active === 2 || active === 3) {
                // Always hide the sidebar
                changeVisibility(sidebar, false);
            } else {
                // Normal behavior
                if (
                    isMouseOverSidebar ||
                    eventParent.clientX <= hideThreshold ||
                    isResizing
                ) {
                    // Show sidebar
                    changeVisibility(sidebar, true);
                } else {
                    // Hide sidebar
                    changeVisibility(sidebar, false);
                }
            }
        }
    }

    // Resize the chat list while the mouse is moving
    function resizeChatList(e) {
        if (isResizing) {
            const sidebar = document.querySelector(sidebarElementSelector);
            const containerWidth = sidebar.parentElement.clientWidth;
            let newFlexBasis =
                ((e.clientX - sidebar.getBoundingClientRect().left) /
                    containerWidth) *
                100;

            if (newFlexBasis >= 10 && newFlexBasis <= 100) {
                userDefinedFlexBasis = newFlexBasis;
                sidebar.style.maxWidth = `${userDefinedFlexBasis}%`;
                sidebar.style.minWidth = `${userDefinedFlexBasis}%`;
            }

            if (!userResizedOnce) {
                userResizedOnce = true;
                setSessionStorageItem("userResizedOnce", "true");
                let resizeHandle = document.getElementById("resize-handle");
                resizeHandle.style.backgroundColor = "transparent";
            }
            setLocalStorageItem("userDefinedFlexBasis", userDefinedFlexBasis);
        }
    }

    // Stop resizing when the mouse button is released
    function stopResize() {
        isResizing = false;
        document.removeEventListener("mousemove", resizeChatList);
        document.removeEventListener("mouseup", stopResize);
    }

    // Check if mouse is over the element or its children
    function isMouseOver(element) {
        return element.contains(eventParent.target);
    }

    // Calculate distance between two points
    function calculateDistance(pos1, pos2) {
        return Math.sqrt(
            Math.pow(pos1.x - pos2.x, 2) + Math.pow(pos1.y - pos2.y, 2)
        );
    }

    // Check if mouse is near the locked position
    function isNearLockPosition(x, y, lockPosition) {
        const distance = calculateDistance(lockPosition, { x, y });
        return distance <= clickThreshold;
    }

    // Helper function to show notifications
    function showNotification(message, time = 5000) {
        // if previous notification found, remove it
        const previousNotification = document.querySelector(
            "div.notification[role='alert']"
        );
        if (previousNotification) {
            document.body.removeChild(previousNotification);
        }

        const notification = document.createElement("div");
        notification.setAttribute("role", "alert");
        notification.classList.add("notification");
        notification.innerHTML = message;
        notification.style.position = "fixed";
        notification.style.top = "50%";
        notification.style.left = "50%";
        notification.style.transform = "translate(-50%, -50%)";
        notification.style.padding = "10px";
        notification.style.backgroundColor = "#333";
        notification.style.color = "#fff";
        notification.style.fontFamily = "Arial, sans-serif";
        notification.style.fontSize = "20px";
        notification.style.borderRadius = "5px";
        notification.style.zIndex = "100000";
        notification.style.opacity = "0";
        notification.style.transition = "opacity 0.5s";
        document.body.appendChild(notification);

        // Fade in the notification
        setTimeout(() => {
            notification.style.opacity = "1";
        }, 100);

        // Fade out and remove the notification after 3 seconds
        setTimeout(() => {
            notification.style.opacity = "0";
            setTimeout(() => {
                if (document.body.contains(notification))
                    document.body.removeChild(notification);
            }, 500);
        }, time);
    }

    function changeActiveState(newactivestate, message = null, time = 3000) {
        active = newactivestate;
        setSessionStorageItem("active", active);
        if (message === null) {
            message = notificationBasedOnActiveState(active);
        }
        if (message) {
            showNotification(message, time);
        }
    }

    function changeBlurEffectState(newBlurEffectState) {
        blurEffect = newBlurEffectState;
        setSessionStorageItem("blurEffect", blurEffect);
    }

    function createToolBar() {
        const customToolbar = document.createElement("div");
        customToolbar.id = "customToolbar";
        document.body.appendChild(customToolbar);

        const overlay = document.createElement("div");
        overlay.id = "overlay";
        document.body.appendChild(overlay);

        const overlayButton = document.createElement("button");
        overlayButton.id = "overlayButton";
        overlayButton.classList.add("eye");
        if (blurEffect) {
            overlayButton.innerHTML = "🙈";
        } else {
            overlayButton.innerHTML = "🐵";
        }
        overlayButton.addEventListener("click", function () {
            const overlayButton = document.getElementById("overlayButton");
            if (blurEffect) {
                overlayButton.innerHTML = "🐵";
                createBlurEffect(false);
            } else {
                overlayButton.innerHTML = "🙈";
                createBlurEffect(true);
            }
        });
        customToolbar.appendChild(overlayButton);

        const githubLink = document.createElement("a");
        githubLink.id = "githubLink";
        githubLink.href = "https://github.com/imxitiz";
        githubLink.innerHTML = "imxitiz";
        customToolbar.appendChild(githubLink);
    }

    function changeVisibilityOfToolbar(show = true) {
        const customToolbar = document.getElementById("customToolbar");
        if (show) {
            customToolbar.style.right = "0";
        } else {
            customToolbar.style.right = "-200px";
        }
    }

    function updateToolBarVisibility() {
        // if hovering in right side
        if (
            eventParent.clientX >= window.innerWidth - hideThreshold ||
            isMouseOver(document.getElementById("customToolbar"))
        ) {
            changeVisibilityOfToolbar(true);
        } else {
            changeVisibilityOfToolbar(false);
        }
    }

    createToolBar();
    function createBlurEffect(show = false) {
        let blurEffectElement = document.getElementById("blur-effect");

        changeBlurEffectState(show);

        if (!blurEffectElement) {
            // Create the blur effect element only once
            blurEffectElement = document.createElement("div");
            blurEffectElement.id = "blur-effect";
            blurEffectElement.style.position = "fixed";
            blurEffectElement.style.top = "0";
            blurEffectElement.style.left = "0";
            blurEffectElement.style.width = "100%";
            blurEffectElement.style.height = "100%";
            blurEffectElement.style.backgroundColor = "rgba(0, 0, 0, 0)";
            blurEffectElement.style.zIndex = "100000";
            blurEffectElement.style.backdropFilter = "blur(0px)";
            blurEffectElement.style.transition =
                "background-color 0.5s ease, backdrop-filter 0.5s ease";
            blurEffectElement.style.display = "none"; // Initially hidden

            function preventDefaultAndStopPropagation(event) {
                event.preventDefault();
                event.stopPropagation();
            }

            // Add common event listeners
            blurEffectElement.addEventListener(
                "click",
                preventDefaultAndStopPropagation
            );
            blurEffectElement.addEventListener(
                "contextmenu",
                preventDefaultAndStopPropagation
            );
            blurEffectElement.addEventListener(
                "mousedown",
                preventDefaultAndStopPropagation
            );
            blurEffectElement.addEventListener("mousemove", function (event) {
                if (event.clientX >= window.innerWidth - hideThreshold) {
                    changeVisibilityOfToolbar(true);
                } else {
                    changeVisibilityOfToolbar(false);
                }
                preventDefaultAndStopPropagation(event);
            });
            document.body.appendChild(blurEffectElement);
        }

        // Toggle the visibility of the blur effect
        if (show) {
            blurEffectElement.style.display = "block";
            setTimeout(() => {
                blurEffectElement.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
                blurEffectElement.style.backdropFilter = "blur(10px)";
            }, 10); // Slight delay to trigger transition
            activateShortcutBlocker();
        } else {
            blurEffectElement.style.backgroundColor = "rgba(0, 0, 0, 0)";
            blurEffectElement.style.backdropFilter = "blur(0px)";
            setTimeout(() => {
                blurEffectElement.style.display = "none";
            }, 500); // Hide element after transition completes
            deactivateShortcutBlocker();
        }
    }

    function blockKeyboardShortcuts(event) {
        event.preventDefault();
        event.stopPropagation();
    }

    function activateShortcutBlocker() {
        document.addEventListener("keydown", blockKeyboardShortcuts, true);
    }

    function deactivateShortcutBlocker() {
        document.removeEventListener("keydown", blockKeyboardShortcuts, true);
    }

    function notificationBasedOnActiveState(activeState) {
        if (activeState == 0) {
            return "Now chat list is shown on hover.";
        } else if (activeState == 1) {
            return "Now chat list is always visible.";
        } else if (activeState == 2) {
            return "Now chat list is always hidden, but locked.";
        } else if (activeState == 3) {
            return "Now chat list is always hidden, but not locked.";
        } else {
            return null;
        }
    }

    // Triple click event listener
    function handleClick(event, clickcount = 0) {
        if (event.detail === 3 || clickcount === 3) {
            const inboxSwitcher = document.querySelector(
                inboxSwitcherElementSelector
            );
            if (active === 0) {
                if (isMouseOver(inboxSwitcher)) {
                    changeActiveState(1);
                } else {
                    // if triple right click then lock hidden
                    if (event.button === 2) {
                        active = 2;
                        lockPosition = { x: event.clientX, y: event.clientY };
                        setSessionStorageItem(
                            "lockPosition",
                            JSON.stringify(lockPosition)
                        );
                        showNotification(
                            "You have to triple click exactly here to unlock the chat list."
                        );
                    } else {
                        // else or if triple left click then unlocked hidden
                        changeActiveState(3);
                    }
                }
            } else if (active === 1) {
                if (isMouseOver(inboxSwitcher)) {
                    changeActiveState(0);
                } else {
                    if (event.button === 2) {
                        active = 2;
                        lockPosition = { x: event.clientX, y: event.clientY };
                        setSessionStorageItem(
                            "lockPosition",
                            JSON.stringify(lockPosition)
                        );
                        showNotification(
                            "You have to triple click exactly here to unlock the chat list."
                        );
                    } else {
                        changeActiveState(3);
                    }
                }
            } else if (active === 2) {
                if (wrongLockedPlaceAttempt > 16) {
                    // no ned to check anything, just leave
                    return;
                }
                if (
                    isNearLockPosition(
                        event.clientX,
                        event.clientY,
                        lockPosition
                    )
                ) {
                    // only right triple click can open the lock
                    if (event.button === 2) {
                        wrongLockedPlaceAttempt = 0;
                        changeActiveState(0);
                    }
                } else {
                    if (wrongLockedPlaceAttempt <= 16) {
                        wrongLockedPlaceAttempt++;
                    }
                    if (wrongLockedPlaceAttempt >= 15) {
                        showNotification(
                            "You're banned! Please refresh the page to start again. <br>OR<br> You can always logout and login again to reset the lock position."
                        );
                    } else if (wrongLockedPlaceAttempt >= 10) {
                        showNotification(
                            "You can always logout and login again to reset the lock position."
                        );
                    } else {
                        if (wrongLockedPlaceAttempt >= 5) {
                            showNotification(
                                "Please logout and login again to reset the lock position."
                            );
                        }
                    }
                    const clickPosition = {
                        x: event.clientX,
                        y: event.clientY,
                    };
                    const distance = calculateDistance(
                        lockPosition,
                        clickPosition
                    );
                    if (distance <= clickThreshold * 1.2) {
                        showNotification(
                            "You are near the locked position, try again!!!"
                        );
                    }
                }
            } else if (active === 3) {
                if (isMouseOver(inboxSwitcher)) {
                    changeActiveState(1);
                } else {
                    if (event.button === 2) {
                        active = 2;
                        lockPosition = { x: event.clientX, y: event.clientY };
                        setSessionStorageItem(
                            "lockPosition",
                            JSON.stringify(lockPosition)
                        );
                        showNotification(
                            "You have to triple click exactly here to unlock the chat list."
                        );
                    } else {
                        changeActiveState(0);
                    }
                }
            }
            // Immediately reflect the change
            changeVisibility(
                document.querySelector(sidebarElementSelector),
                active === 1
            );
            setSessionStorageItem("active", active);
        }
    }

    // Initialize the script
    function init() {
        initialize();
        document.addEventListener("mousemove", function (event) {
            eventParent = event;
            updateSidebarVisibility();
            updateToolBarVisibility();
        });

        document.addEventListener("click", handleClick);

        let lastRightClickTime = 0;
        let rightClickCount = 0;

        document.addEventListener("mousedown", function (event) {
            if (event.button != 2) {
                return;
            }
            const avoidElements = [
                "IMG",
                "VIDEO",
                "AUDIO",
                "SOURCE",
                "A",
                "BUTTON",
                "INPUT",
                "SELECT",
                "TEXTAREA",
                "IFRAME",
                "EMBED",
                "OBJECT",
                "CANVAS",
                "SVG",
            ];
            if (
                avoidElements.includes(event.target.tagName) ||
                event.target.isContentEditable ||
                event.target.getAttribute("role") === "textbox" ||
                window.getSelection().toString().trim().length > 0
            ) {
                return;
            }
            event.preventDefault();

            const currentTime = new Date().getTime();
            if (currentTime - lastRightClickTime < 500) {
                rightClickCount++;
            } else {
                rightClickCount = 1;
            }
            lastRightClickTime = currentTime;

            handleClick(event, rightClickCount);
        });
    }

    // Add styles
    let styles = `
        #customToolbar {
            position: fixed;
            top: 48%;
            right: -200px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            background: linear-gradient(
                            45deg, 
                            rgba(255, 69, 0, 0.8), /* Red-Orange */
                            rgba(255, 140, 0, 0.8), /* Orange */
                            rgba(255, 215, 0, 0.8)  /* Yellow */
                        );
            z-index: 1000000;
            padding: 5px;
            border-radius: 10px;
            transition: right 0.5s ease;
        }
        
        
        #overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: none;
            z-index: 99999;
        }
            
        #overlayButton {
            background-color: #333;
            color: #fff;
            border: none;
            padding: 8px;
            margin: 5px 10px;
            cursor: pointer;
            border-radius: 10px;
            font-size: 2.5rem;
        }
        
        #overlayButton:hover {
            background-color: #555;
            filter: drop-shadow(0 0 5px #fff);
        }
            
        #githubLink {
            color: #285ed0;
            text-decoration: none;
            font-size: 1.5rem;
            font-weight: bold;
            margin: 5px 0;
        }
            
        #githubLink:hover {
            text-decoration: underline;
        }
    `;

    let styleElement = document.createElement("style");
    styleElement.textContent = styles;
    document.head.appendChild(styleElement);

    init();
})();