// ==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();
});