MooMoo.io Private Chat Room

Chat with your friends privately

// ==UserScript==
// @name         MooMoo.io Private Chat Room
// @description  Chat with your friends privately
// @author       KOOKY WARRIOR
// @match        *://*.moomoo.io/*
// @icon         https://moomoo.io/img/favicon.png?v=1
// @require      https://cdnjs.cloudflare.com/ajax/libs/peerjs/1.4.7/peerjs.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js
// @license      MIT
// @version      0.1
// @namespace    https://greasyfork.org/users/999838
// ==/UserScript==

/*
1) To get started, press "Shift" + "Enter"
2) Now you should be able to see 4 buttons:
- Join Room (Join a room via room ID)
- Create Room (Create a room)
- Show/Hide (Show or hide the chat container)
- Open/Close Editor (Edit the position of the chat container by dragging it around / Change the size of the chat container at the bottom right of the chat container)
3) After you have joined or created a room, press "Shift" + "Enter" to activate the private chat input message holder.
4) Once you have finished typing your message, press "Enter" to send the message to the chat room
5) If you want to clear the chat, type "/clear" and then press "Enter"
*/

;(() => {
	let myID = null
	let conn = null
	let peer = null
	let connected = false
	let editing = false

	const gameUI = document.getElementById("gameUI")
	const gameCanvas = document.getElementById("gameCanvas")

	const style = document.createElement("style")
	style.innerHTML = /*css*/ `
        #chatContainer {
			background-color: rgba(0, 0, 0, 0.1);
            position: absolute;
			padding: 10px;
			pointer-events: all;
			word-break: break-word;
			overflow-x: hidden;
        }
		#chatContainer::-webkit-scrollbar {
			width: 0.6em;
		}
		#chatContainer::-webkit-scrollbar-thumb {
			background-color: white;
		}
		#chatContainer::-webkit-scrollbar-track {
			background-color: none !important;
		}
        #roomChatSettingsContainer {
            width: 100%;
            position: fixed;
            text-align: center;
            top: 50%;
            transform: translateY(-50%);
            -ms-transform: translateY(-50%);
            -moz-transform: translateY(-50%);
            -webkit-transform: translateY(-50%);
            -o-transform: translateY(-50%);
        }
        .roomChatSettingButton {
            margin: 10px;
        }
        #roomChatBoxInput {
            padding: 6px;
            font-size: 20px;
            color: #fff;
            background-color: rgba(0, 0, 0, 0.25);
            -webkit-border-radius: 4px;
            -moz-border-radius: 4px;
            border-radius: 4px;
            pointer-events: all;
            border: 0;
        }
        #roomChatBoxInput:focus {
            outline: none;
        }
		#popupContainer {
			width: 100%;
            position: fixed;
            text-align: center;
            top: 50%;
            transform: translateY(-50%);
            -ms-transform: translateY(-50%);
            -moz-transform: translateY(-50%);
            -webkit-transform: translateY(-50%);
            -o-transform: translateY(-50%);
		}
		#popupContainerInput {
			padding: 10px;
            font-size: 26px;
            color: #fff;
            background-color: rgba(0, 0, 0, 0.25);
            -webkit-border-radius: 4px;
            -moz-border-radius: 4px;
            border-radius: 4px;
            pointer-events: all;
            border: 0;
		}
		#popupContainerInput:focus {
			outline: none;
		}
		.chatmsgholder {
			width: 100%;
			color: white;
			font-size: 22px;
		}
		.systemmsgholder {
			width: 100%;
			color: #fadadc;
			font-size: 22px;
		}
    	`
	document.head.appendChild(style)
	const style2 = document.createElement("link")
	style2.rel = "stylesheet"
	style2.href = "https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/themes/le-frog/jquery-ui.min.css"
	document.head.appendChild(style2)

	const chatContainer = document.createElement("div")
	chatContainer.id = "chatContainer"
	chatContainer.style.overflowY = "auto"
	chatContainer.style.width = localStorage.getItem("room_chat_width") ? localStorage.getItem("room_chat_width") : "15%"
	chatContainer.style.height = localStorage.getItem("room_chat_height") ? localStorage.getItem("room_chat_height") : "30%"
	chatContainer.style.top = localStorage.getItem("room_chat_top") ? localStorage.getItem("room_chat_top") : "0%"
	chatContainer.style.left = localStorage.getItem("room_chat_left") ? localStorage.getItem("room_chat_left") : "0%"
	chatContainer.style.display = "none"
	gameUI.appendChild(chatContainer)

	const roomChatSettingsContainer = document.createElement("div")
	roomChatSettingsContainer.style.display = "none"
	roomChatSettingsContainer.id = "roomChatSettingsContainer"

	const joinRoomButton = document.createElement("div")
	joinRoomButton.className = "roomChatSettingButton storeTab"
	joinRoomButton.textContent = "Join Room"
	roomChatSettingsContainer.appendChild(joinRoomButton)

	const createRoomButton = document.createElement("div")
	createRoomButton.className = "roomChatSettingButton storeTab"
	createRoomButton.textContent = "Create Room"
	roomChatSettingsContainer.appendChild(createRoomButton)

	const copyRoomIDButton = document.createElement("div")
	copyRoomIDButton.className = "roomChatSettingButton storeTab"
	copyRoomIDButton.textContent = "Copy Room ID"
	copyRoomIDButton.style.display = "none"
	roomChatSettingsContainer.appendChild(copyRoomIDButton)

	const leaveRoomButton = document.createElement("div")
	leaveRoomButton.className = "roomChatSettingButton storeTab"
	leaveRoomButton.textContent = "Leave Room"
	leaveRoomButton.style.display = "none"
	roomChatSettingsContainer.appendChild(leaveRoomButton)

	const deleteRoomButton = document.createElement("div")
	deleteRoomButton.className = "roomChatSettingButton storeTab"
	deleteRoomButton.textContent = "Delete Room"
	deleteRoomButton.style.display = "none"
	roomChatSettingsContainer.appendChild(deleteRoomButton)

	const toggleHideButton = document.createElement("div")
	toggleHideButton.className = "roomChatSettingButton storeTab"
	toggleHideButton.textContent = "Show"
	roomChatSettingsContainer.appendChild(toggleHideButton)

	const editStyleButton = document.createElement("div")
	editStyleButton.className = "roomChatSettingButton storeTab"
	editStyleButton.textContent = "Open Editor"
	roomChatSettingsContainer.appendChild(editStyleButton)

	function mousedown(event) {
		gameCanvas.dispatchEvent(
			new MouseEvent("mousedown", {
				button: event.button
			})
		)
	}
	function mouseup(event) {
		gameCanvas.dispatchEvent(
			new MouseEvent("mouseup", {
				button: event.button
			})
		)
	}
	function mousemove(event) {
		gameCanvas.dispatchEvent(
			new MouseEvent("mousemove", {
				clientX: event.clientX,
				clientY: event.clientY
			})
		)
	}
	chatContainer.onmousedown = mousedown
	chatContainer.onmouseup = mouseup
	chatContainer.onmousemove = mousemove
	editStyleButton.onclick = function () {
		if (editing) {
			editing = false
			editStyleButton.textContent = "Open Editor"
			chatContainer.style.overflowY = "auto"
			$("#chatContainer").resizable("destroy")
			$("#chatContainer").draggable("destroy")
			chatContainer.onmousedown = mousedown
			chatContainer.onmouseup = mouseup
			chatContainer.onmousemove = mousemove
			gameCanvas.style.pointerEvents = "auto"
		} else {
			editing = true
			editStyleButton.textContent = "Close Editor"
			chatContainer.style.overflowY = "hidden"
			if (chatContainer.style.display == "none") {
				chatContainer.style.display = "block"
				toggleHideButton.textContent = "Hide"
			}
			chatContainer.onmousedown = null
			chatContainer.onmouseup = null
			chatContainer.onmousemove = null
			gameCanvas.style.pointerEvents = "none"
			$("#chatContainer").resizable({
				containment: "#gameUI",
				stop: function (event, ui) {
					const width = (ui.size.width / window.innerWidth) * 100 + "%"
					const height = (ui.size.height / window.innerHeight) * 100 + "%"
					localStorage.setItem("room_chat_width", width)
					localStorage.setItem("room_chat_height", height)
					chatContainer.style.width = width
					chatContainer.style.height = height
				}
			})
			$("#chatContainer").draggable({
				containment: "#gameUI",
				stop: function (event, ui) {
					const top = (ui.position.top / window.innerHeight) * 100 + "%"
					const left = (ui.position.left / window.innerWidth) * 100 + "%"
					localStorage.setItem("room_chat_top", top)
					localStorage.setItem("room_chat_left", left)
					chatContainer.style.top = top
					chatContainer.style.left = left
				}
			})
		}
	}
	toggleHideButton.onclick = function () {
		if (chatContainer.style.display == "none") {
			chatContainer.style.display = "block"
			toggleHideButton.textContent = "Hide"
		} else {
			chatContainer.style.display = "none"
			toggleHideButton.textContent = "Show"
			if (editing) {
				editing = false
				editStyleButton.textContent = "Open Editor"
				chatContainer.style.overflowY = "auto"
				$("#chatContainer").resizable("destroy")
				$("#chatContainer").draggable("destroy")
				chatContainer.onmousedown = mousedown
				chatContainer.onmouseup = mouseup
				chatContainer.onmousemove = mousemove
				gameCanvas.style.pointerEvents = "auto"
			}
		}
	}
	document.getElementById("chatHolder").appendChild(roomChatSettingsContainer)

	const roomChatBoxInput = document.createElement("input")
	roomChatBoxInput.setAttribute("autocomplete", "off")
	roomChatBoxInput.setAttribute("aria-autocomplete", "none")
	roomChatBoxInput.id = "roomChatBoxInput"
	roomChatBoxInput.type = "text"
	roomChatBoxInput.placeholder = "Enter Private Message"
	roomChatBoxInput.maxLength = 50
	roomChatBoxInput.style.display = "none"
	document.getElementById("chatHolder").appendChild(roomChatBoxInput)

	const popupContainer = document.createElement("div")
	popupContainer.id = "popupContainer"
	popupContainer.style.display = "none"

	const popupContainerInput = document.createElement("input")
	popupContainerInput.setAttribute("autocomplete", "off")
	popupContainerInput.setAttribute("aria-autocomplete", "none")
	popupContainerInput.id = "popupContainerInput"
	popupContainerInput.className = "roomChatSettingButton"
	popupContainerInput.type = "text"
	popupContainerInput.placeholder = "Enter username"
	popupContainer.appendChild(popupContainerInput)

	const popupContainerokButton = document.createElement("div")
	popupContainerokButton.className = "roomChatSettingButton storeTab"
	popupContainerokButton.textContent = "OK"
	popupContainer.appendChild(popupContainerokButton)

	const popupContainercancelButton = document.createElement("div")
	popupContainercancelButton.className = "roomChatSettingButton storeTab"
	popupContainercancelButton.textContent = "Cancel"
	popupContainercancelButton.onclick = function () {
		popupContainer.style.display = "none"
		if (document.getElementById("chatHolder").style.display != "none") {
			roomChatSettingsContainer.style.display = "block"
		}
	}
	popupContainer.appendChild(popupContainercancelButton)
	document.getElementById("chatHolder").appendChild(popupContainer)

	new MutationObserver((mutations) => {
		mutations.forEach((mutationRecord) => {
			if (document.getElementById("chatHolder").style.display != "block") {
				popupContainer.style.display = "none"
				document.getElementById("chatBox").hidden = false
				roomChatBoxInput.style.display = "none"
				roomChatBoxInput.value = ""
				if (editing) {
					editing = false
					editStyleButton.textContent = "Open Editor"
					chatContainer.style.overflowY = "auto"
					$("#chatContainer").resizable("destroy")
					$("#chatContainer").draggable("destroy")
					chatContainer.onmousedown = mousedown
					chatContainer.onmouseup = mouseup
					chatContainer.onmousemove = mousemove
					gameCanvas.style.pointerEvents = "auto"
				}
			}
		})
	}).observe(document.getElementById("chatHolder"), {
		attributes: true,
		attributeFilter: ["style"]
	})

	let sendMessage = () => {}
	window.addEventListener("keydown", (event) => {
		if (event.code != "Enter") return

		if (event.shiftKey) {
			roomChatSettingsContainer.style.display = "block"
			document.getElementById("chatBox").hidden = true
			if (connected) {
				roomChatBoxInput.style.display = "inline-block"
				const interval = setInterval(() => {
					roomChatBoxInput.focus()
					if (document.activeElement == roomChatBoxInput) {
						clearInterval(interval)
					}
				}, 10)
			}
		} else {
			roomChatSettingsContainer.style.display = "none"
			if (document.getElementById("chatBox").hidden && roomChatBoxInput.value != "") {
				if (connected) {
					sendMessage(roomChatBoxInput.value)
				}
				roomChatBoxInput.value = ""
				document.getElementById("chatBox").hidden = false
				roomChatBoxInput.style.display = "none"
			}
		}
	})

	function copy(text) {
		var input = document.createElement("textarea")
		input.innerHTML = text
		document.body.appendChild(input)
		input.select()
		var result = document.execCommand("copy")
		document.body.removeChild(input)
		return result
	}

	function showSystemMessage(msg) {
		const needToScroll = chatContainer.scrollTop + chatContainer.clientHeight >= chatContainer.scrollHeight - 5
		const container = document.createElement("div")
		container.className = "systemmsgholder"
		const systemmsgholder = document.createElement("span")
		systemmsgholder.innerText = "* " + msg
		container.appendChild(systemmsgholder)
		chatContainer.appendChild(container)
		if (chatContainer.childElementCount > 250) {
			chatContainer.removeChild(chatContainer.firstChild)
		}
		if (needToScroll) {
			chatContainer.scrollTop = chatContainer.scrollHeight
		}
	}

	function showChatMessage(owner, msg) {
		const needToScroll = chatContainer.scrollTop + chatContainer.clientHeight >= chatContainer.scrollHeight - 5
		const container = document.createElement("div")
		container.className = "chatmsgholder"
		const nameholder = document.createElement("span")
		nameholder.innerText = owner + ": "
		container.appendChild(nameholder)
		const chatmsgholder = document.createElement("span")
		chatmsgholder.innerText = msg.substring(0, 50)
		chatmsgholder.style.color = "rgba(255, 255, 255, 0.6)"
		container.appendChild(chatmsgholder)
		chatContainer.appendChild(container)
		if (chatContainer.childElementCount > 250) {
			chatContainer.removeChild(chatContainer.firstChild)
		}
		if (needToScroll) {
			chatContainer.scrollTop = chatContainer.scrollHeight
		}
	}

	let myselfLeft = false
	joinRoomButton.addEventListener("click", () => {
		peer = new Peer()
		peer.on("open", async (id) => {
			myID = id
			if (myID == null) return

			popupContainerInput.value = ""
			popupContainerInput.placeholder = "Enter Room ID"
			popupContainer.style.display = "block"
			roomChatSettingsContainer.style.display = "none"
			popupContainerInput.focus()
			popupContainerokButton.onclick = function () {
				const roomID = popupContainerInput.value
				if (roomID == null || roomID == "") {
					document.getElementById("chatButton").click()
					return
				}

				popupContainerInput.value = ""
				popupContainerInput.placeholder = "Enter Username"
				popupContainer.style.display = "block"
				roomChatSettingsContainer.style.display = "none"
				popupContainerInput.focus()
				popupContainerokButton.onclick = function () {
					document.getElementById("chatButton").click()
					const name = popupContainerInput.value
					if (name == null || name == "") return

					conn = peer.connect(roomID)
					conn.on("open", () => {
						connected = true
						chatContainer.innerHTML = ""
						chatContainer.style.display = "block"
						toggleHideButton.textContent = "Hide"
						conn.on("data", (data) => {
							if (data.type === 0) {
								conn.send({
									name: name,
									peerID: myID,
									type: 0
								})
							} else if (data.type === 1) {
								// SYSTEM MESSAGE
								showSystemMessage(data.msg)
							} else if (data.type === 2) {
								// SOMEONE SEND MESSAGE
								showChatMessage(data.owner, data.msg)
							}
						})
					})

					myselfLeft = false
					conn.on("close", () => {
						document.getElementById("chatButton").click()
						connected = false
						joinRoomButton.style.display = "inline-block"
						createRoomButton.style.display = "inline-block"
						copyRoomIDButton.style.display = "none"
						leaveRoomButton.style.display = "none"
						if (!myselfLeft) {
							showSystemMessage("You left the chat room due to either a lost connection or the chat room being closed")
						}
					})

					sendMessage = (msg) => {
						if (msg == "/clear") {
							chatContainer.innerHTML = ""
							return
						}
						conn.send({
							type: 1,
							peerID: myID,
							msg: msg
						})
					}

					joinRoomButton.style.display = "none"
					createRoomButton.style.display = "none"
					copyRoomIDButton.onclick = function () {
						copy(roomID)
					}
					copyRoomIDButton.style.display = "inline-block"
					leaveRoomButton.style.display = "inline-block"
					leaveRoomButton.onclick = function () {
						document.getElementById("chatButton").click()
						myselfLeft = true
						conn.close()
						peer.destroy()
						connected = false
						joinRoomButton.style.display = "inline-block"
						createRoomButton.style.display = "inline-block"
						copyRoomIDButton.style.display = "none"
						leaveRoomButton.style.display = "none"
						showSystemMessage("You left the chat room")
					}
				}
			}
		})
	})

	createRoomButton.addEventListener("click", () => {
		peer = new Peer()
		peer.on("open", (id) => {
			myID = id
			if (myID == null) return

			popupContainerInput.value = ""
			popupContainerInput.placeholder = "Enter Username"
			popupContainer.style.display = "block"
			roomChatSettingsContainer.style.display = "none"
			popupContainerInput.focus()
			popupContainerokButton.onclick = function () {
				document.getElementById("chatButton").click()
				const name = popupContainerInput.value
				if (name == null || name == "") return

				connected = true
				chatContainer.innerHTML = ""
				chatContainer.style.display = "block"
				toggleHideButton.textContent = "Hide"
				let allConnections = [
					{
						name: name + "[1]",
						peerID: myID,
						conn: null
					}
				]
				let usernameCounter = 1
				showSystemMessage("Room created")
				copy(myID)
				showSystemMessage("Room ID copied")
				peer.on("connection", (connection) => {
					connection.on("open", () => {
						connection.on("data", (data) => {
							if (data.type === 0) {
								// RECEIVED JOIN DATA (peerID, name, type)
								usernameCounter++
								allConnections.push({
									name: `${data.name}[${usernameCounter}]`,
									peerID: data.peerID,
									conn: connection
								})
								allConnections.forEach((e) => {
									if (e.conn == null) return

									e.conn.send({
										type: 1,
										peerID: data.peerID,
										msg: `${data.name}[${usernameCounter}] has joined the chat room`
									})
								})
								showSystemMessage(`${data.name}[${usernameCounter}] has joined the chat room`)
							} else if (data.type === 1) {
								// RECEIVE SEND MESSAGE (peerID, type, msg)
								for (let index = 0; index < allConnections.length; index++) {
									if (allConnections[index].peerID == data.peerID) {
										allConnections.forEach((e) => {
											if (e.conn == null) return
											e.conn.send({
												type: 2,
												owner: allConnections[index].name,
												msg: data.msg
											})
										})
										showChatMessage(allConnections[index].name, data.msg)
										break
									}
								}
							}
						})
						connection.send({ type: 0 })
					})

					const otherpeerID = connection.peer
					connection.on("close", () => {
						for (let index = 0; index < allConnections.length; index++) {
							if (allConnections[index].peerID == otherpeerID) {
								const name = allConnections[index].name
								allConnections.splice(index, 1)
								allConnections.forEach((e) => {
									if (e.conn == null) return
									e.conn.send({
										type: 1,
										peerID: otherpeerID,
										msg: `${name} has left the chat room`
									})
								})
								showSystemMessage(`${name} has left the chat room`)
								break
							}
						}
					})
				})

				sendMessage = (msg) => {
					if (msg == "/clear") {
						chatContainer.innerHTML = ""
						return
					}
					allConnections.forEach((e) => {
						if (e.conn == null) return
						e.conn.send({
							type: 2,
							owner: name + "[1]",
							msg: msg
						})
					})
					showChatMessage(name + "[1]", msg)
				}

				joinRoomButton.style.display = "none"
				createRoomButton.style.display = "none"
				copyRoomIDButton.onclick = function () {
					copy(myID)
					showSystemMessage("Room ID copied")
				}
				copyRoomIDButton.style.display = "inline-block"
				deleteRoomButton.style.display = "inline-block"
				deleteRoomButton.onclick = function () {
					document.getElementById("chatButton").click()
					peer.destroy()
					connected = false
					joinRoomButton.style.display = "inline-block"
					createRoomButton.style.display = "inline-block"
					copyRoomIDButton.style.display = "none"
					deleteRoomButton.style.display = "none"
				}
			}
		})
	})
})()