diff --git a/client/Terminal-00-Multiuser.user.js b/client/Terminal-00-Multiuser.user.js index 452bd03..9a73674 100644 --- a/client/Terminal-00-Multiuser.user.js +++ b/client/Terminal-00-Multiuser.user.js @@ -1,10 +1,11 @@ // ==UserScript== // @name MultiProbe // @namespace https://*.angusnicneven.com/* -// @version 20240508.1 +// @version 20240527.1 // @description Probe with friends! // @author tgpholly // @match https://*.angusnicneven.com/* +// @match https://*.heavenonline.xyz/* // @icon https://www.google.com/s2/favicons?sz=64&domain=angusnicneven.com // @grant none // ==/UserScript== @@ -35,6 +36,12 @@ if (!window.TE_ACTIVE) { const windowLocation = window.location.href; window.multiprobe_debug = false; +const SITE_DEFAULT_CURSOR = { + "localhost": "https://angusnicneven.com/cursor/rrw.png", + "angusnicneven.com": "https://angusnicneven.com/cursor/rrw.png", + "heavenonline.xyz": "https://heavenonline.xyz/core/cursor/frame1.png" +}; + console.log("[MP] MultiProbe init"); (function() { @@ -59,15 +66,17 @@ console.log("[MP] MultiProbe init"); Clients: 4, ClientLeft: 5, Ping: 6, - GroupData: 7 + GroupData: 7, + HonkShoe: 8 }; let cursorImageI = window.getComputedStyle(document.body).cursor; + const cssCursor = `${cursorImageI === "auto" || !cursorImageI.includes("url") ? `url(${SITE_DEFAULT_CURSOR[window.location.href.split("//")[1].split("/")[0].split(":")[0]]}) 11 11, auto` : cursorImageI}`; console.log("[MP] Injecting custom styles..."); const styles = document.createElement("style"); styles.innerHTML = ` html { - cursor: ${cursorImageI}; + cursor: ${cssCursor}; } #otherCursors { @@ -111,6 +120,18 @@ html { z-index: -1!important; } +.eepy { + position: absolute; + top: 0; + right: 0; + width: 64px; + height: 64px; + image-rendering: pixelated; + background: url("data:image/webp;base64,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"); + z-index: 1; + transform: translate(60px, -60px); +} + .grouphidden { right: -12rem!important; } @@ -284,9 +305,9 @@ kbd { setInterval(() => { if (document.body.scrollHeight > window.innerHeight) { - otherCursors.style = `width:${clientWidth = (document.body.getBoundingClientRect().width + bodyMarginRight + bodyMarginLeft)}px;height:${document.body.scrollHeight}px;top:-${((window.scrollY + document.body.getBoundingClientRect().top) - bodyMarginTop)}px;cursor: ${cursorImageI};`; + otherCursors.style = `width:${clientWidth = (document.body.getBoundingClientRect().width + bodyMarginRight + bodyMarginLeft)}px;height:${document.body.scrollHeight}px;top:-${((window.scrollY + document.body.getBoundingClientRect().top) - bodyMarginTop)}px;cursor: ${cssCursor};`; } else { - otherCursors.style = `width:${clientWidth = (document.body.getBoundingClientRect().width + bodyMarginRight + bodyMarginLeft)}px;height:${window.innerHeight}px;top:-${((window.scrollY + document.body.getBoundingClientRect().top) - bodyMarginTop)}px;cursor: ${cursorImageI};`; + otherCursors.style = `width:${clientWidth = (document.body.getBoundingClientRect().width + bodyMarginRight + bodyMarginLeft)}px;height:${window.innerHeight}px;top:-${((window.scrollY + document.body.getBoundingClientRect().top) - bodyMarginTop)}px;cursor: ${cssCursor};`; } }, 1000); @@ -307,7 +328,7 @@ kbd { }); }); - const keepAlivePacket = createWriter(Endian.LE, 1).writeByte(MessageType.KeepAlive).toBuffer(); + const keepAlivePacket = createWriter(Endian.LE, 1).writeUByte(MessageType.KeepAlive).toBuffer(); let remoteClients = new FunkyArray(); @@ -320,7 +341,7 @@ kbd { let lastSendTime = 0; class RemoteClient { - constructor(name) { + constructor(name, startAfk, isSelfCursor = false) { this.name = name; this.element = document.createElement("div"); this.element.style.position = "absolute"; @@ -337,6 +358,12 @@ kbd { clientName.style = "position:absolute;left:100%;top:100%;background-color:black;padding:4px 8px;color:white;font-size:12px;font-family:Arial,sans-serif;"; clientName.innerText = name; this.element.appendChild(clientName); + if (!isSelfCursor) { + this.eepy = document.createElement("div"); + this.eepy.className = "eepy"; + this.eepy.style = startAfk ? "" : "display:none"; + this.element.appendChild(this.eepy); + } otherCursors.appendChild(this.element); this.targetX = 0; this.targetY = 0; @@ -348,6 +375,10 @@ kbd { this.element.visibility = "hidden"; } + setAfk(afkState) { + this.eepy.style = afkState ? "" : "display:none"; + } + rawSetPos(x, y) { if (!this.hasBeenMoved) { this.element.visibility = ""; @@ -401,6 +432,17 @@ kbd { } } + let isAfkLocal = false; + let lastSendAfkState = false + + window.onfocus = (e) => { + isAfkLocal = false; + } + + window.onblur = (e) => { + isAfkLocal = true; + } + let rawMouseX = 0; let rawMouseY = 0; @@ -422,10 +464,10 @@ kbd { } } - function log(type, text) { - const d = new Date(); - console.log(`[hNET] [${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(4, "0")}] [${type}] ${text}`); - } + function log(type, text) { + const d = new Date(); + console.log(`[hNET] [${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(4, "0")}] [${type}] ${text}`); + } let allowedPings = 10; window.onkeypress = (e) => { @@ -433,7 +475,7 @@ kbd { if (ws && ready) { if (allowedPings > 0) { allowedPings--; - ws.send(createWriter(Endian.LE, 9).writeByte(MessageType.Ping).writeFloat((rawMouseX + window.scrollX - 32) / clientWidth).writeInt(rawMouseY + window.scrollY - 32).toBuffer()); + ws.send(createWriter(Endian.LE, 9).writeUByte(MessageType.Ping).writeFloat((rawMouseX + window.scrollX - 32) / clientWidth).writeInt(rawMouseY + window.scrollY - 32).toBuffer()); } } } else if (e.key === "n") { @@ -471,9 +513,13 @@ kbd { lastSendTime = performance.now(); oldMouseX = currentMouseX; oldMouseY = currentMouseY; - ws.send(createWriter(Endian.LE, 9).writeByte(MessageType.CursorPos).writeFloat(oldMouseX / clientWidth).writeInt(currentMouseY).toBuffer()); + ws.send(createWriter(Endian.LE, 9).writeUByte(MessageType.CursorPos).writeFloat(oldMouseX / clientWidth).writeInt(currentMouseY).toBuffer()); } } + if (isAfkLocal !== lastSendAfkState) { + lastSendAfkState = isAfkLocal; + ws.send(createWriter(Endian.LE, 2).writeUByte(MessageType.HonkShoe).writeBool(isAfkLocal).toBuffer()); + } } if ((performance.now() - timeSinceLastPingReset) >= 1000) { @@ -506,13 +552,13 @@ kbd { ws.onopen = () => { console.log("[MP] Connected! Authenticating..."); otherCursors.innerHTML = ""; - selfCursor = new RemoteClient(localStorage["t00mp_username"]); + selfCursor = new RemoteClient(localStorage["t00mp_username"], false, true); selfCursor.probeImage.style.visibility = "hidden"; selfCursor.element.style.visibility = localStorage["t00mp_cursorStyle"] ?? "hidden"; selfCursor.hasBeenMoved = true; - const currentPage = windowLocation.split("/").slice(3).join("/"); + const currentPage = windowLocation.split("/").slice(2).join("/"); ws.send(createWriter(Endian.LE, 4 + apiKey.length + currentPage.length) - .writeByte(MessageType.ClientDetails) + .writeUByte(MessageType.ClientDetails) .writeShortString(apiKey) .writeString(currentPage) .toBuffer()); @@ -532,7 +578,8 @@ kbd { const clientName = reader.readShortString(); const clientX = reader.readFloat(); const clientY = reader.readInt(); - remoteClients.set(clientId, new RemoteClient(clientName)).rawSetPosInit(clientX, clientY); + const isAfk = reader.readBool(); + remoteClients.set(clientId, new RemoteClient(clientName, isAfk)).rawSetPosInit(clientX, clientY); } if (window.multiprobe_debug) { log("RECV", `Initial client packet, got ${clientCount} clients.`); @@ -552,9 +599,9 @@ kbd { { const clientId = reader.readUInt(); const clientName = reader.readShortString(); - remoteClients.set(clientId, new RemoteClient(clientName)); + remoteClients.set(clientId, new RemoteClient(clientName, false)); if (window.multiprobe_debug) { - log("RECV", `New client joined page: ${clientName} ID=${clientId}`); + log("RECV", `New client joined page: ${clientName} ID=${clientId}`); } break; } @@ -566,7 +613,7 @@ kbd { const cursorY = reader.readInt(); remoteClients.get(clientId).rawSetPos(cursorX, cursorY); if (window.multiprobe_debug) { - log("RECV", `Cursor position update for ${clientId}, X=${cursorX}, Y=${cursorY}`); + log("RECV", `Cursor position update for ${clientId}, X=${cursorX}, Y=${cursorY}`); } } break; @@ -576,7 +623,7 @@ kbd { const clientId = reader.readUInt(); removeClient(clientId); if (window.multiprobe_debug) { - log("RECV", `Client ${clientId} left or switched pages`); + log("RECV", `Client ${clientId} left or switched pages`); } break; } @@ -586,7 +633,7 @@ kbd { const cursorY = reader.readInt(); createPing(cursorX * clientWidth, cursorY); if (window.multiprobe_debug) { - log("RECV", `Got a ping, X=${cursorX}, Y=${cursorY}`); + log("RECV", `Got a ping, X=${cursorX}, Y=${cursorY}`); } break; } @@ -595,20 +642,38 @@ kbd { groupUIBase.style = ""; groupUsers.innerHTML = ""; groupTitle.innerText = reader.readShortString(); - const groupUserCount = reader.readUShort(); + const groupUserCount = reader.readUShort(); if (window.multiprobe_debug) { - log("RECV", `Server sent group information for "${groupTitle.innerText}" (${groupUserCount} clients)`); + log("RECV", `Server sent group information for "${groupTitle.innerText}" (${groupUserCount} clients)`); } for (let i = 0; i < groupUserCount; i++) { const groupUsername = reader.readShortString(); const groupUserLocation = reader.readString(); if (window.multiprobe_debug) { - log("RECV", `[GROUP USER] USERNAME=${groupUsername}, LOCATION=${groupUserLocation}`); + log("RECV", `[GROUP USER] USERNAME=${groupUsername}, LOCATION=${groupUserLocation}`); } createGroupUser(groupUsername, groupUserLocation); } break; } + case MessageType.HonkShoe: + { + const clientId = reader.readUInt(); + const isAfk = reader.readBool(); + + if (remoteClients.has(clientId)) { + remoteClients.get(clientId).setAfk(isAfk); + if (window.multiprobe_debug) { + if (isAfk) { + log("RECV", `Client ${clientId} went afk`); + } else { + log("RECV", `Client ${clientId} is no longer afk`); + } + } + } + + break; + } } } diff --git a/server/enums/MessageType.ts b/server/enums/MessageType.ts index a123e9c..7e20a88 100644 --- a/server/enums/MessageType.ts +++ b/server/enums/MessageType.ts @@ -6,5 +6,6 @@ export enum MessageType { Clients, ClientLeft, Ping, - GroupData + GroupData, + HonkShoe } \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index 0341885..745f95a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -94,7 +94,7 @@ fastify.get("/", async (req, res) => { }); fastify.get("/account", async (req, res) => { - return "TODO"; + return res.redirect(302, "/"); }); fastify.get("/account/login", async (req, res) => { @@ -359,6 +359,16 @@ function sendToAllInGroup(user:RemoteUser, data:Buffer) { }); } +const afkInterval = setInterval(() => { + users.forEach(otherUser => { + if (Date.now() - otherUser.timeLastMovedCursor >= 30000 && !otherUser.isAfk) { + otherUser.isAfk = true; + const afkPacket = createWriter(Endian.LE, 6).writeByte(MessageType.HonkShoe).writeUInt(otherUser.id).writeBool(otherUser.isAfk).toBuffer(); + sendToAllButSelf(otherUser, afkPacket); + } + }); +}, 5000); + websocketServer.on("connection", (socket) => { const myUUID = crypto.randomUUID(); let user:RemoteUser; @@ -412,7 +422,7 @@ websocketServer.on("connection", (socket) => { socket.on("message", async (data) => { const reader = createReader(Endian.LE, data as Buffer); if (reader.length > 0 && reader.length < 1024) { - switch (reader.readByte()) { + switch (reader.readUByte()) { case MessageType.KeepAlive: { user.lastKeepAliveTime = Date.now(); @@ -447,9 +457,9 @@ websocketServer.on("connection", (socket) => { lengthOfUsernames += otherUser.username.length + 1; // + 1 for length byte } }); - const usersToSend = createWriter(Endian.LE, 3 + (usersOnPage.length * 12) + lengthOfUsernames).writeByte(MessageType.Clients).writeUShort(usersOnPage.length); + const usersToSend = createWriter(Endian.LE, 3 + (usersOnPage.length * 13) + lengthOfUsernames).writeByte(MessageType.Clients).writeUShort(usersOnPage.length); for (const otherUser of usersOnPage) { - usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY); + usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY).writeBool(otherUser.isAfk); } if (dbParty) { user = users.set(myUUID, new RemoteUser(socket, myUUID, dbUser.Username, page, rawURL, dbUser.Id, dbParty.Id, dbParty.Name)); @@ -469,6 +479,13 @@ websocketServer.on("connection", (socket) => { user.cursorX = reader.readFloat(); user.cursorY = reader.readInt(); sendToAllButSelf(user, createWriter(Endian.LE, 13).writeByte(MessageType.CursorPos).writeUInt(user.id).writeFloat(user.cursorX).writeInt(user.cursorY).toBuffer()); + + user.timeLastMovedCursor = Date.now(); + if (user.isAfk) { + user.isAfk = false; + const afkPacket = createWriter(Endian.LE, 6).writeByte(MessageType.HonkShoe).writeUInt(user.id).writeBool(user.isAfk).toBuffer(); + sendToAllButSelf(user, afkPacket); + } break; } case MessageType.Ping: @@ -491,6 +508,19 @@ websocketServer.on("connection", (socket) => { } break; } + case MessageType.HonkShoe: + { + if (user === undefined) { + return; + } + + user.isAfk = reader.readBool(); + + const afkPacket = createWriter(Endian.LE, 6).writeByte(MessageType.HonkShoe).writeUInt(user.id).writeBool(user.isAfk).toBuffer(); + sendToAllButSelf(user, afkPacket); + + break; + } } } }); @@ -507,6 +537,7 @@ function shutdown() { websocketServer.close(async () => { await fastify.close(); clearInterval(sessionExpiryInterval); + clearInterval(afkInterval); Console.cleanup(); console.log("Goodbye!"); diff --git a/server/objects/RemoteUser.ts b/server/objects/RemoteUser.ts index c590768..1c1c580 100644 --- a/server/objects/RemoteUser.ts +++ b/server/objects/RemoteUser.ts @@ -17,6 +17,8 @@ export default class RemoteUser { public groupId:number = Number.MIN_VALUE; public groupName:string; public lastKeepAliveTime:number; + public isAfk:boolean; + public timeLastMovedCursor: number; constructor(socket:WebSocket, connectionUUID:string, username:string, currentURL:string, rawURL:string, userId:number, groupId:number, groupName:string) { this.socket = socket; @@ -31,6 +33,8 @@ export default class RemoteUser { this.groupId = groupId; this.groupName = groupName; this.lastKeepAliveTime = Date.now(); + this.isAfk = false; + this.timeLastMovedCursor = Date.now(); } send(data:Buffer) {