BLF Voice Chat Script

Allows you to use voice chat on Bullet Force

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         BLF Voice Chat Script
// @namespace    http://tampermonkey.net/
// @license      GPL-3
// @version      2024-09-09
// @description  Allows you to use voice chat on Bullet Force
// @author       You
// @match        https://games.crazygames.com/en_US/bullet-force-multiplayer/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=crazygames.com
// @grant        none
// ==/UserScript==

let ws = undefined;

class UIManager {
    constructor() {
        this.UIContext = null;
        this.UIMenus = [];
        this.tabs = [];
        this.notificationStack = [];
        this.notificationHeight = 100;
        this.notificationMargin = 10;
    }

    getAllTabs() {
        return this.tabs;
    }

    createNotification(titleText, descriptionText) {
        const notificationContainer = document.createElement('div');
        notificationContainer.className = 'notification-popup';
        notificationContainer.style.position = 'fixed';
        notificationContainer.style.left = '10px';
        notificationContainer.style.bottom = this.calculateNotificationBottom() + 'px';
        notificationContainer.style.transform = 'translateY(100%)';
        notificationContainer.style.backgroundColor = '#0e0e0e';
        notificationContainer.style.color = '#ffffff';
        notificationContainer.style.width = '300px';
        notificationContainer.style.padding = '20px';
        notificationContainer.style.borderRadius = '8px';
        notificationContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        notificationContainer.style.zIndex = '9999';
        notificationContainer.style.transition = 'transform 0.3s ease-in-out';

        const title = document.createElement('h2');
        title.textContent = titleText;
        title.style.fontSize = '22px';
        title.style.textAlign = 'center';
        title.style.marginBottom = '10px';
        title.classList.add('rainbow-animation');

        const description = document.createElement('p');
        description.textContent = descriptionText;
        description.style.fontSize = '16px';
        description.style.textAlign = 'center';
        description.classList.add('rainbow-animation');

        notificationContainer.appendChild(title);
        notificationContainer.appendChild(description);

        document.body.appendChild(notificationContainer);

        setTimeout(() => {
            notificationContainer.style.transform = 'translateY(0)';
        }, 50);

        setTimeout(() => {
            notificationContainer.style.transform = 'translateY(100%)';
            setTimeout(() => {
                this.removeNotification(notificationContainer);
                document.body.removeChild(notificationContainer);
            }, 300);
        }, 5000);

        this.makeDraggable(notificationContainer);

        this.notificationStack.push(notificationContainer);
    }

    calculateNotificationBottom() {
        let totalHeight = this.notificationMargin;
        this.notificationStack.forEach(notification => {
            totalHeight += notification.offsetHeight + this.notificationMargin;
        });
        return totalHeight;
    }

    removeNotification(notification) {
        const index = this.notificationStack.indexOf(notification);
        if (index !== -1) {
            this.notificationStack.splice(index, 1);
        }
        this.repositionNotifications();
    }

    repositionNotifications() {
        let totalHeight = this.notificationMargin;
        this.notificationStack.forEach(notification => {
            notification.style.bottom = totalHeight + 'px';
            totalHeight += notification.offsetHeight + this.notificationMargin;
        });
    }

    createMenu(elementId, titleText, width = '300px', height = 'auto') {
        const container = document.createElement('div');
        container.id = elementId;
        container.style.position = 'fixed';
        container.style.backgroundColor = '#0e0e0e';
        container.style.borderRadius = '8px';
        container.style.padding = '20px';
        container.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        container.style.zIndex = '9999';
        container.style.width = width;
        container.style.height = height;

        container.style.top = `calc(50% - (${height} / 2))`;
        container.style.left = `calc(50% - (${width} / 2))`;

        container.style.userSelect = 'none';

        container.style.overflowY = 'auto';
        container.className = 'custom-scrollbar';

        const title = document.createElement('h2');
        title.textContent = titleText;
        title.style.color = '#ffffff';
        title.style.marginBottom = '20px';
        title.style.fontSize = '22px';
        title.style.textAlign = 'center';
        title.style.marginTop = '0px';
        title.classList.add('rainbow-animation');

        container.appendChild(title);

        document.body.appendChild(container);

        this.UIContext = container;

        return container;
    }

    makeDraggable(element) {
        let offsetX, offsetY;

        function handleMouseDown(event) {
            event.preventDefault();
            const boundingRect = element.getBoundingClientRect();
            offsetX = event.clientX - boundingRect.left;
            offsetY = event.clientY - boundingRect.top;

            console.log(`x: ${event.clientX}, y: ${event.clientY}, Offsetx: ${offsetX}, Offsety: ${offsetY}`)

            document.addEventListener('mousemove', handleMouseMove);
            document.addEventListener('mouseup', handleMouseUp);
        }

        function handleMouseMove(event) {
            moveElement(event.clientX, event.clientY);
        }

        function moveElement(clientX, clientY) {
            element.style.left = clientX - offsetX + 'px';
            element.style.top = clientY - offsetY + 'px';
        }

        function handleMouseUp() {
            cleanupListeners();
        }

        function handleTouchEnd() {
            cleanupListeners();
        }

        function cleanupListeners() {
            document.removeEventListener('mousemove', handleMouseMove);
            document.removeEventListener('mouseup', handleMouseUp);
        }

        const titleBar = element.querySelector('h2');
        titleBar.addEventListener('mousedown', handleMouseDown);

        element.style.position = 'absolute';
        titleBar.style.cursor = 'move';
        titleBar.style.userSelect = 'none';
    }

    addButton(buttonText, buttonAction) {
        const button = document.createElement('button');
        button.style.width = '100%';
        button.style.padding = '10px';
        button.style.backgroundColor = '#1c1c1c';
        button.style.color = '#ffffff';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.marginBottom = '10px';
        button.style.fontWeight = 'bold';
        button.style.fontSize = '16px';
        button.addEventListener('click', buttonAction);
        button.classList.add('rainbow-animation');

        const buttonTextSpan = document.createElement('span');
        buttonTextSpan.textContent = buttonText;
        button.appendChild(buttonTextSpan);

        this.UIContext.appendChild(button);

        return button;
    }

    addLabel(labelText) {
        const label = document.createElement('h3');
        label.textContent = labelText;
        label.style.color = '#ffffff';
        label.style.marginBottom = '20px';
        label.style.fontSize = '18px';
        label.style.textAlign = 'center';
        label.classList.add('rainbow-animation');

        this.UIContext.appendChild(label);

        return label;
    }

    addSpacer(height) {
        const spacer = document.createElement('div');
        spacer.style.width = '100%';
        spacer.style.height = `${height}px`;
        spacer.style.marginBottom = `${height}px`;

        this.UIContext.appendChild(spacer);

        return spacer;
    }

    addTextInput(placeholderText, valueChangeAction) {
        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = placeholderText;
        input.style.width = 'calc(100% - 1px)';
        input.style.padding = '10px';
        input.style.marginBottom = '20px';
        input.style.borderRadius = '5px';
        input.addEventListener('input', valueChangeAction);
        input.style.backgroundColor = '#0e0e0e';
        input.classList.add('rainbow-animation');
        input.focus();

        this.UIContext.appendChild(input);
        input.focus();

        return input;
    }

    addSlider(min, max, step, currentValue, customText, valueChangeAction) {
        let textBubble = undefined;
        let hideTimeout = null;

        const sliderContainer = document.createElement('div');
        sliderContainer.style.width = 'calc(100% - 1px)';
        sliderContainer.style.marginBottom = '20px';
        sliderContainer.style.position = 'relative';

        const slider = document.createElement('input');
        slider.type = 'range';
        slider.min = min;
        slider.max = max;
        slider.value = currentValue;
        slider.step = step;
        slider.style.width = '100%';
        slider.style.borderRadius = '5px';

        const showBubble = () => {
            clearTimeout(hideTimeout);
            textBubble.style.opacity = 1;
            hideTimeout = setTimeout(() => {
                textBubble.style.opacity = 0;
            }, 3000);
        };

        slider.addEventListener('input', () => {
            valueChangeAction(slider.value);
            textBubble.textContent = `${customText}: ${slider.value}`;
            const sliderWidth = slider.offsetWidth;
            const bubbleWidth = textBubble.offsetWidth;
            const sliderValue = slider.value;
            const newPosition = (sliderValue / (max - min)) * sliderWidth;
            const adjustedPosition = Math.min(Math.max(newPosition, bubbleWidth / 2), sliderWidth - bubbleWidth / 2);
            textBubble.style.left = `${adjustedPosition}px`;
            showBubble();
        });

        slider.addEventListener('mousedown', showBubble);
        slider.addEventListener('touchstart', showBubble);

        slider.classList.add('rainbow-animation');

        const bubble = document.createElement('div');
        bubble.style.position = 'absolute';
        bubble.style.top = 'calc(100% + 10px)';
        bubble.style.left = '50%';
        bubble.style.transform = 'translateX(-50%)';
        bubble.style.backgroundColor = '#f0f0f0';
        bubble.style.padding = '5px 10px';
        bubble.style.borderRadius = '5px';
        bubble.style.backgroundColor = '#181818';
        bubble.style.whiteSpace = 'nowrap';
        bubble.style.minWidth = '100px';
        bubble.style.transition = 'opacity 0.5s';
        bubble.style.opacity = 0;
        bubble.textContent = `${customText}: ${currentValue}`;
        textBubble = bubble;

        sliderContainer.appendChild(bubble);
        sliderContainer.appendChild(slider);

        this.contentContainer.appendChild(sliderContainer);

        return slider;
    }

    addLogo() {
        const logo = document.createElement('img');
        logo.src = 'https://github.com/Snoofz/Hailware-Assets/blob/main/snowly-icon.png?raw=true';
        logo.className = 'logo';
        logo.alt = 'Logo';
        logo.style.marginLeft = '35%';
        logo.classList.add('hue-shift-animation');

        this.UIContext.insertBefore(logo, this.UIContext.firstChild);

        return logo;
    }

    createTabMenu(tabs) {
        const tabContainer = document.createElement('div');
        tabContainer.style.display = 'flex';
        tabContainer.style.borderBottom = '1px solid #cc0000';
        tabContainer.style.marginBottom = '20px';
        tabContainer.classList.add('rainbow-animation')

        const contentContainers = tabs.map(() => document.createElement('div'));

        tabs.forEach((tab, index) => {
            const tabButton = document.createElement('button');
            tabButton.textContent = tab.title;
            tabButton.style.flex = '1';
            tabButton.style.padding = '10px';
            tabButton.style.backgroundColor = '#1c1c1c';
            tabButton.style.color = '#ffffff';
            tabButton.style.border = 'none';
            tabButton.style.cursor = 'pointer';
            tabButton.style.fontWeight = 'bold';
            tabButton.style.fontSize = '16px';
            tabButton.classList.add('rainbow-animation');

            tabButton.addEventListener('click', () => {
                contentContainers.forEach((container, idx) => {
                    if (idx !== index) {
                        container.style.display = 'none';
                    }
                });
                contentContainers[index].style.display = 'block';
            });

            this.tabs.push(tabButton);
            tabContainer.appendChild(tabButton);

            const uiTab = new UITab(tab.title, contentContainers[index], document.createElement('div'));
            uiTab.content.innerHTML = tab.content;
            tab.uiTab = uiTab;
        });

        this.UIContext.appendChild(tabContainer);

        contentContainers.forEach(container => {
            container.style.display = 'none';
            this.UIContext.appendChild(container);
        });

        if (contentContainers.length > 0) {
            contentContainers[0].style.display = 'block';
        }

        return {
            UITabs: tabs,
            Containers: contentContainers
        };
    }

    addTabsToTabMenu(existingTabs, newTabs) {
        const contentContainers = newTabs.map(() => document.createElement('div'));

        newTabs.forEach((tab, index) => {
            const tabButton = document.createElement('button');
            tabButton.textContent = tab.title;
            tabButton.style.flex = '1';
            tabButton.style.padding = '10px';
            tabButton.style.backgroundColor = '#1c1c1c';
            tabButton.style.color = '#ffffff';
            tabButton.style.border = 'none';
            tabButton.style.cursor = 'pointer';
            tabButton.style.fontWeight = 'bold';
            tabButton.style.fontSize = '16px';
            tabButton.classList.add('rainbow-animation');

            tabButton.addEventListener('click', () => {
                contentContainers.forEach((container, idx) => {
                    if (idx !== index) {
                        container.style.display = 'none';
                    }
                });
                contentContainers[index].style.display = 'block';
            });

            existingTabs.push(tabButton);
            const uiTab = new UITab(tab.title, contentContainers[index], document.createElement('div'));
            uiTab.content.innerHTML = tab.content;
            tab.uiTab = uiTab;
        });

        existingTabs.forEach(tab => {
            this.UIContext.appendChild(tab);
        });

        contentContainers.forEach(container => {
            container.style.display = 'none';
            this.UIContext.appendChild(container);
        });

        if (contentContainers.length > 0) {
            contentContainers[0].style.display = 'block';
        }
    }

    showTabContent(index, tabs, contentContainer) {
        contentContainer.innerHTML = '';

        const content = document.createElement('div');
        content.innerHTML = tabs[index].content;
        content.style.color = '#ffffff';
        content.style.fontSize = '16px';
        contentContainer.appendChild(content);

        this.activeTabContent = content;
    }
}

class UITab {
    constructor(title, contentContainer, content) {
        this.title = title;
        this.contentContainer = contentContainer;
        this.content = content;
        this.isHidden = true;
    }

    static getContentContainer() {
        return this.contentContainer;
    }

    clear() {
        while (this.contentContainer.firstChild) {
            this.contentContainer.removeChild(this.contentContainer.firstChild);
        }
    }

    addButton(buttonText, buttonAction) {
        const button = document.createElement('button');
        button.style.width = '100%';
        button.style.padding = '10px';
        button.style.backgroundColor = '#1c1c1c';
        button.style.color = '#ffffff';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.marginBottom = '10px';
        button.style.fontWeight = 'bold';
        button.style.fontSize = '16px';
        button.addEventListener('click', buttonAction);
        button.classList.add('rainbow-animation');

        const buttonTextSpan = document.createElement('span');
        buttonTextSpan.textContent = buttonText;
        button.appendChild(buttonTextSpan);

        this.contentContainer.appendChild(button);

        return button;
    }

    addLabel(labelText) {
        const label = document.createElement('h3');
        label.innerHTML = labelText;
        label.style.color = '#ffffff';
        label.style.marginBottom = '20px';
        label.style.fontSize = '18px';
        label.style.textAlign = 'center';
        label.classList.add('rainbow-animation');

        this.contentContainer.appendChild(label);

        return label;
    }

    addTextInput(placeholderText, valueChangeAction) {
        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = placeholderText;
        input.style.width = 'calc(100% - 1px)';
        input.style.padding = '10px';
        input.style.marginBottom = '20px';
        input.style.borderRadius = '5px';
        input.addEventListener('input', valueChangeAction);
        input.style.backgroundColor = '#0e0e0e';
        input.classList.add('rainbow-animation');
        input.focus();
        this.contentContainer.appendChild(input);
        input.focus();
        return input;
    }

    addSpacer(height) {
        const spacer = document.createElement('div');
        spacer.style.width = '100%';
        spacer.style.height = `${height}px`;
        spacer.style.marginBottom = `${height}px`;

        this.contentContainer.appendChild(spacer);

        return spacer;
    }

    addSlider(min, max, step, currentValue, customText, valueChangeAction) {
        let textBubble = undefined;
        let hideTimeout = null;

        const sliderContainer = document.createElement('div');
        sliderContainer.style.width = 'calc(100% - 1px)';
        sliderContainer.style.marginBottom = '20px';
        sliderContainer.style.position = 'relative';

        const slider = document.createElement('input');
        slider.type = 'range';
        slider.min = min;
        slider.max = max;
        slider.value = currentValue;
        slider.step = step;
        slider.style.width = '100%';
        slider.style.borderRadius = '5px';

        const showBubble = () => {
            clearTimeout(hideTimeout);
            textBubble.style.opacity = 1;
            hideTimeout = setTimeout(() => {
                textBubble.style.opacity = 0;
            }, 3000);
        };

        slider.addEventListener('input', () => {
            valueChangeAction(slider.value);
            textBubble.textContent = `${customText}: ${slider.value}`;
            const sliderWidth = slider.offsetWidth;
            const bubbleWidth = textBubble.offsetWidth;
            const sliderValue = slider.value;
            const newPosition = (sliderValue / (max - min)) * sliderWidth;
            const adjustedPosition = Math.min(Math.max(newPosition, bubbleWidth / 2), sliderWidth - bubbleWidth / 2);
            textBubble.style.left = `${adjustedPosition}px`;
            showBubble();
        });

        slider.addEventListener('mousedown', showBubble);
        slider.addEventListener('touchstart', showBubble);

        slider.classList.add('rainbow-animation');

        const bubble = document.createElement('div');
        bubble.style.position = 'absolute';
        bubble.style.top = 'calc(100% + 10px)';
        bubble.style.left = '50%';
        bubble.style.transform = 'translateX(-50%)';
        bubble.style.backgroundColor = '#f0f0f0';
        bubble.style.padding = '5px 10px';
        bubble.style.borderRadius = '5px';
        bubble.style.backgroundColor = '#181818';
        bubble.style.whiteSpace = 'nowrap';
        bubble.style.minWidth = '100px';
        bubble.style.transition = 'opacity 0.5s';
        bubble.style.opacity = 0;
        bubble.textContent = `${customText}: ${currentValue}`;
        textBubble = bubble;

        sliderContainer.appendChild(bubble);
        sliderContainer.appendChild(slider);

        this.contentContainer.appendChild(sliderContainer);

        return slider;
    }

    showContent() {
        const allTabs = this.contentContainer.parentElement.querySelectorAll('.tab-content');
        allTabs.forEach(tab => {
            tab.style.display = 'none';
        });

        if (this.isHidden) {
            this.contentContainer.style.display = 'block';
            this.isHidden = false;
        }
    }
}

class Log {
    static info(message) {
        console.log(`%c${message.toUpperCase()}`, 'font-size: 18px; color: #7289da;');
    }

    static tool(message) {
        console.log(`%c${message.toUpperCase()}`, 'font-size: 18px; color: #FFB6C1;');
    }

    static welcome(message) {
        console.log(`%c${message.toUpperCase()}`, 'font-size: 25px; color: #ff0000;');
    }

    static error(message) {
        console.error(`%c${message.toUpperCase()}`, 'font-size: 18px; color: #dc3545;');
    }

    static success(message) {
        console.log(`%c${message.toUpperCase()}`, 'font-size: 18px; color: #28a745;');
    }
}

let isRecording = false;
let mediaRecorder;
let audioChunks = [];
let audioContext = new AudioContext();

function waitForUnityInstance(callback) {
    const interval = setInterval(() => {
        const unityInstance = Crazygames.getUnityInstance();
        if (unityInstance && unityInstance.SendMessage) {
            clearInterval(interval);
            setTimeout(() => {
                callback();
            }, 3500);
        }
    }, 1000);
}

function generateRandomChannelId() {
    return "Lobby" + Math.floor(Math.random() * 42992);
}

function generateRandomPartyId(username) {
    return username + "'s Party" + Math.floor(Math.random() * 42992);
}

let uiManager = new UIManager();

let reconnectInterval = 1000;
const maxReconnectInterval = 30000;
let currentChannel = generateRandomChannelId();


function switchChannel(newChannelId) {
    ws.send(JSON.stringify({
        type: 'join',
        channelId: newChannelId
    }));
    currentChannel = newChannelId;
}

function setUsername(newUsername) {
    ws.send(JSON.stringify({
        "type": "setusername",
        "username": newUsername
    }));
}

let mutes = [];
let userVolumes = [];
let selfUsername = "";
let isInParty = false;
let tabs = undefined;
let updatedVolumes = [];

waitForUnityInstance(() => {
   (function() {
    const originalFetch = window.fetch;

    window.fetch = async function(...args) {
        const options = args[1] || {};

        if (args[0].includes("https://server.blayzegames.com/OnlineAccountSystem/leftV2.php")) {
            if (isInParty) return;
            switchChannel(generateRandomChannelId());
            tabs[1].uiTab.clear();
        }

        if (args[0].includes('https://server.blayzegames.com/OnlineAccountSystem/community/is_live_now.php') && options.body instanceof Blob) {
            console.log('Body is a Blob for account roles:');

            const reader = new FileReader();
            reader.onloadend = function() {
                const blobContent = reader.result;

                const params = new URLSearchParams(blobContent);
                const username = params.get('username');

                if (username) {
                    console.log('Parsed Username:', username);
                    selfUsername = username;
                    setUsername(username);
                } else {
                    console.log('Username not found in the request body.');
                }
            };
            reader.readAsText(options.body);
        }

        else if (args[0].includes('https://server.blayzegames.com/OnlineAccountSystem/store-match/register_in_store_match.php') && options.body instanceof Blob) {
            console.log('Body is a Blob for store match:');

            const reader = new FileReader();
            reader.onloadend = function() {
                const blobContent = reader.result;

                const params = new URLSearchParams(blobContent);
                const gameName = params.get('game_name');

                if (gameName) {
                    console.log('Parsed Game Name:', decodeURIComponent(gameName));
                    switchChannel(decodeURIComponent(gameName));
                } else {
                    console.log('Game name not found in the request body.');
                }
            };
            reader.readAsText(options.body);
        }

        else if (options.body) {
            if (options.headers && options.headers['Content-Type'] && options.headers['Content-Type'].includes('application/json')) {
                console.log('Body (JSON):', JSON.parse(options.body));
            } else {
                console.log('Body:', options.body);
            }
        } else {
            console.log('No body in request.');
        }

        // Intercept and modify the response for "get-account-rolesV2.php"
        if (args[0].includes('https://server.blayzegames.com/OnlineAccountSystem/get-account-rolesV2.php')) {
            const response = await originalFetch.apply(this, args);

            const modifiedBody = {
                status: 3,
                role: 6,
                creator: 1
            };

            // Create a new response with the modified body
            const modifiedResponse = new Response(JSON.stringify(modifiedBody), {
                status: response.status,
                statusText: response.statusText,
                headers: response.headers
            });

            return modifiedResponse; // Return the modified response
        }

        // For other requests, just return the original fetch
        return originalFetch.apply(this, args);
    };
})();

    function setUpVC() {
        let mainMenu = uiManager.createMenu("epicUI", "Bullet Force VC Module", "400px", "500px");
        uiManager.makeDraggable(mainMenu);

        tabs = uiManager.createTabMenu([{
                title: 'Devices',
                content: '<p>This is the content of Tab 1</p>'
            },
            {
                title: 'Users',
                content: '<p>This is the content of Tab 2</p>'
            },
            {
                title: 'Invites',
                content: '<p>This is the content of Tab 3</p>'
            },
            {
                title: 'Recent Users',
                content: '<p>This is the content of Tab 3</p>'
            },
        ]);

        tabs = tabs.UITabs;

        let recentUsers = JSON.parse(localStorage.getItem('recentUsers')) || [];

        tabs[3].uiTab.clear();
        recentUsers.forEach(username => {
            tabs[3].uiTab.addLabel(`${username}`);

            tabs[3].uiTab.addButton(`Invite ${username} to party`, () => {
                let party = generateRandomPartyId(selfUsername);
                ws.send(JSON.stringify({
                    type: 'invite',
                    username: username,
                    channelId: party,
                    inviterName: selfUsername
                }));
                switchChannel(party);
                isInParty = true;
            });
        });

        navigator.mediaDevices.enumerateDevices()
            .then(devices => {
                devices.forEach(device => {
                    console.log(`${device.kind}: ${device.label} (ID: ${device.deviceId})`);
                    tabs[0].uiTab.addButton(`${device.label}`, () => {
                        setDefaultDevice(device.deviceId, device.kind);
                    });
                });
            })
            .catch(err => {
                console.error("Error accessing devices: ", err);
            });

        ws = new WebSocket('wss://finger.hri7566.info');

        ws.addEventListener("open", () => {
            console.log("Connected to WebSocket server");
            switchChannel(currentChannel);
        });

        ws.addEventListener("message", async (e) => {
            try {
                let data = JSON.parse(e.data);
                console.log(data);

                if (data.type === "receiveInvite") {
                    uiManager.createNotification("VC", `You've been invited to ${data.inviterName}'s party!`);
                    tabs[2].uiTab.addButton(`Join ${data.inviterName}'s Party`, () => {
                        switchChannel(data.channelId);
                        isInParty = true;
                        tabs[2].uiTab.clear();
                    });
                }

                if (data.type === "inviteConfirmation") {
                    uiManager.createNotification("VC", `You've invited ${data.userInvited} to a party!`);
                }

                if (data.type === "userJoin") {
                    const user = { username: data.username, volume: 100 };
                    userVolumes.push(user);

                    tabs[1].uiTab.addLabel(`${data.username}`);

                    let storedMutes = JSON.parse(localStorage.getItem('mutes')) || [];
                    let isMuted = storedMutes.includes(data.username);
                    let muteButtonLabel = isMuted ? `Unmute ${data.username}` : `Mute ${data.username}`;

                    let muteButton = tabs[1].uiTab.addButton(muteButtonLabel, () => {
                        if (isMuted) {
                            storedMutes = storedMutes.filter(username => username !== data.username);
                            localStorage.setItem('mutes', JSON.stringify(storedMutes));
                            isMuted = false;
                            muteButton.textContent = `Mute ${data.username}`;
                            console.log(`${data.username} has been unmuted`);
                            mutes = mutes.filter(username => username !== data.username);
                        } else {
                            storedMutes.push(data.username);
                            localStorage.setItem('mutes', JSON.stringify(storedMutes));
                            isMuted = true;
                            muteButton.textContent = `Unmute ${data.username}`;
                            console.log(`${data.username} has been muted`);
                            mutes.push(data.username);
                        }
                    });

                    let storedVolumes = JSON.parse(localStorage.getItem('userVolumes')) || [];
                    let userVolume = (storedVolumes.find(u => u.username === data.username) || { volume: 100 }).volume;

                    tabs[1].uiTab.addSlider(0, 1, 0.1, userVolume, `Volume for ${data.username}`, (newValue) => {
                        user.volume = newValue;
                        updatedVolumes = storedVolumes.filter(u => u.username !== data.username);
                        updatedVolumes.push({ username: data.username, volume: newValue });
                        localStorage.setItem('userVolumes', JSON.stringify(updatedVolumes));
                        console.log(`Updated volume for ${data.username}: ${newValue}`);
                    });

                    let recentUsers = JSON.parse(localStorage.getItem('recentUsers')) || [];

                    recentUsers = recentUsers.filter(u => u !== data.username);

                    recentUsers.unshift(data.username);

                    if (recentUsers.length > 10) recentUsers = recentUsers.slice(0, 10);

                    localStorage.setItem('recentUsers', JSON.stringify(recentUsers));

                    tabs[3].uiTab.clear();
                    recentUsers.forEach(username => {
                        tabs[3].uiTab.addLabel(`${username}`);

                        tabs[3].uiTab.addButton(`Invite ${username} to party`, () => {
                            let party = generateRandomPartyId(selfUsername);
                            ws.send(JSON.stringify({
                                type: 'invite',
                                username: username,
                                channelId: party,
                                inviterName: selfUsername
                            }));
                            switchChannel(party);
                            isInParty = true;
                        });
                    });
                }

                if (data.type === 'voice') {
                    if (mutes.includes(data.username)) return;

                    const userVolume = updatedVolumes.find(user => user.username === data.username)?.volume || 1.0;

                    let base64Audio = data.voiceData;
                    if (base64Audio && base64Audio.startsWith("data:audio")) {
                        playAudioFromBase64(base64Audio, userVolume);
                    }
                }

            } catch (error) {
                console.error("Error in message event:", error);
            }
        });

        let mediaRecorder;
        let isRecording = false;
        let sendInterval;

        let captureInterval;
        const captureIntervalMs = 1000;

        async function startVoiceCapture() {
            if (!isRecording) {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });

                mediaRecorder.ondataavailable = (event) => {
                    if (event.data.size > 0) {
                        audioChunks.push(event.data);
                    }
                };

                mediaRecorder.onstop = () => {
                    const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
                    let reader = new FileReader();
                    reader.onloadend = () => {
                        let base64AudioMessage = reader.result;
                        ws.send(JSON.stringify({
                            type: 'voice',
                            channelId: currentChannel,
                            voiceData: base64AudioMessage
                        }));
                        audioChunks = [];
                    };
                    reader.readAsDataURL(audioBlob);
                };

                mediaRecorder.start();

                captureInterval = setInterval(() => {
                    if (isRecording) {
                        mediaRecorder.stop();
                        mediaRecorder.start();
                    }
                }, captureIntervalMs);

                isRecording = true;
                console.log("Voice recording started");
                uiManager.createNotification("VC", "Voice chat was enabled");
            }
        }

        function stopVoiceCapture() {
            if (isRecording) {
                clearInterval(captureInterval);
                captureInterval = null;
                mediaRecorder.stop();
                isRecording = false;
                console.log("Voice recording stopped");
                uiManager.createNotification("VC", "Voice chat was disabled");
            }
        }

        let audioContext = new AudioContext();

        function playAudioFromBase64(base64Audio, volume) {
            let audio = new Audio(base64Audio);
            audio.volume = volume;
            audio.play().catch(error => console.error('Playback error:', error));
        }


        function toggleVoiceCapture() {
            if (!isRecording) {
                startVoiceCapture();
            } else {
                stopVoiceCapture();
            }
        }

        function toggleMainMenu() {
            if (mainMenu.style.display === 'none') {
                mainMenu.style.display = 'block';
            } else {
                mainMenu.style.display = 'none';
            }
        }

        function resetMenuPosition() {
            mainMenu.style.top = `calc(50% - (${mainMenu.style.height} / 2))`;
            mainMenu.style.left = `calc(50% - (${mainMenu.style.width} / 2))`;
        }

        document.addEventListener('keydown', function (event) {
            if (event.key === "/") {
                toggleVoiceCapture();
            }
            if (event.key === 'Insert') {
                toggleMainMenu();
            } else if (event.key === 'Delete') {
                resetMenuPosition();
            }
        });

        function setDefaultDevice(deviceId, kind) {
            const constraints = {};

            if (kind === 'audioinput') {
                constraints.audio = { deviceId: { exact: deviceId } };
            } else if (kind === 'videoinput') {
                constraints.video = { deviceId: { exact: deviceId } };
            }

            navigator.mediaDevices.getUserMedia(constraints)
                .then(stream => {
                    console.log(`Default ${kind} set to device with ID: ${deviceId}`);
                })
                .catch(err => {
                    console.error(`Error setting default ${kind}: `, err);
                });
        }
    }
    setUpVC();
});