parties work!

This commit is contained in:
Holly Stubbs 2024-04-26 03:01:06 +01:00
parent b0178cb82d
commit f85bd3fcb8
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
5 changed files with 148 additions and 134 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Terminal 00 Multiuser // @name Terminal 00 Multiuser
// @namespace https://*.angusnicneven.com/* // @namespace https://*.angusnicneven.com/*
// @version 20240420.2 // @version 20240426.1
// @description Probe with friends! // @description Probe with friends!
// @author tgpholly // @author tgpholly
// @match https://*.angusnicneven.com/* // @match https://*.angusnicneven.com/*
@ -35,6 +35,8 @@ if (!window.TE_ACTIVE) {
(function() { (function() {
'use strict'; 'use strict';
const USERSCRIPT_VERSION = "20240426.1";
if (!continueRunningScript) { if (!continueRunningScript) {
return; return;
} }
@ -182,9 +184,21 @@ kbd {
white-space: nowrap; white-space: nowrap;
} }
.mplink, .mplink:visited {
color: #d2738a;
}
.mplink:hover {
color: #c1b492;
}
`.split("\n").join("").split("\r").join("").split("\t").join(""); `.split("\n").join("").split("\r").join("").split("\t").join("");
document.head.appendChild(styles); document.head.appendChild(styles);
if (!localStorage["mpconnectonload"]) {
localStorage["mpconnectonload"] = true;
}
const otherCursors = document.createElement("div"); const otherCursors = document.createElement("div");
otherCursors.id = "otherCursors"; otherCursors.id = "otherCursors";
document.body.appendChild(otherCursors); document.body.appendChild(otherCursors);
@ -220,12 +234,12 @@ kbd {
buttonBox.classList.add("buttons"); buttonBox.classList.add("buttons");
user.appendChild(buttonBox); user.appendChild(buttonBox);
const followButton = document.createElement("button"); /*const followButton = document.createElement("button");
followButton.innerText = "F"; followButton.innerText = "F";
buttonBox.appendChild(followButton); buttonBox.appendChild(followButton);*/
const gotoButton = document.createElement("button"); const gotoButton = document.createElement("button");
gotoButton.innerText = "G"; gotoButton.innerText = "Go To";
gotoButton.onclick = () => window.location.href = location; gotoButton.onclick = () => window.location.href = location;
buttonBox.appendChild(gotoButton); buttonBox.appendChild(gotoButton);
@ -241,6 +255,15 @@ kbd {
otherCursors.style = `width:${clientWidth = document.body.getBoundingClientRect().width}px;height:${window.innerHeight}px`; otherCursors.style = `width:${clientWidth = document.body.getBoundingClientRect().width}px;height:${window.innerHeight}px`;
} }
}, 1000); }, 1000);
/*fetch(`https://git.eusv.net/tgpholly/t00-multiuser/raw/branch/master/client/Terminal-00-Multiuser.user.js?${Date.now()}`).then(res => {
res.text(text => {
if (text.includes("@version")) {
const version = file.split("@version")[1].split("\n")[0].trim().split(".").join("");
if ()
}
});
});*/
const keepAlivePacket = createWriter(Endian.LE, 1).writeByte(MessageType.KeepAlive).toBuffer(); const keepAlivePacket = createWriter(Endian.LE, 1).writeByte(MessageType.KeepAlive).toBuffer();
@ -472,97 +495,6 @@ kbd {
break; break;
} }
case MessageType.ClientLeft: case MessageType.ClientLeft:
{
const clientId = reader.readUInt();
removeClient(clientId);
}
case MessageType.Ping:
{
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
createPing(cursorX * clientWidth, cursorY);
}
}
}
ws.onmessage = (e) => {
e.data.arrayBuffer().then(onMessage);
}
function onCloseAndError() {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
keepAliveInterval = undefined;
}
ws = undefined;
ready = false;
setTimeout(() => doConnect(localStorage["mpapikey"]), 5000);
}
ws.onclose = onCloseAndError;
ws.onerror = onCloseAndError;
}
function doConnect(apiKey) {
const Buffer = getBufferClass();
ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:39195" : "wss://ws.eusv.net/t00mp");
let keepAliveInterval;
ws.onopen = () => {
otherCursors.innerHTML = "";
selfCursor = new RemoteClient(localStorage["t00mp_username"]);
selfCursor.probeImage.style.visibility = "hidden";
selfCursor.element.style.visibility = localStorage["t00mp_cursorStyle"] ?? "hidden";
selfCursor.hasBeenMoved = true;
const currentPage = window.location.href.split("/").slice(3).join("/");
ws.send(createWriter(Endian.LE, 4 + apiKey.length + currentPage.length)
.writeByte(MessageType.ClientDetails)
.writeShortString(apiKey)
.writeString(currentPage)
.toBuffer());
keepAliveInterval = setInterval(() => {
ws.send(keepAlivePacket);
}, 5000);
}
function onMessage(buf) {
const reader = createReader(Endian.LE, Buffer.from(buf));
switch (reader.readByte()) {
case MessageType.Clients:
{
const clientCount = reader.readUShort();
for (let i = 0; i < clientCount; i++) {
const clientId = reader.readUInt();
const clientName = reader.readShortString();
const clientX = reader.readFloat();
const clientY = reader.readInt();
remoteClients.set(clientId, new RemoteClient(clientName)).rawSetPosInit(clientX, clientY);
}
ready = true;
if (windowContainer) {
windowContainer.remove();
windowContainer = null;
}
createFirstTimeDialog();
break;
}
case MessageType.ClientJoined:
{
const clientId = reader.readUInt();
const clientName = reader.readShortString();
remoteClients.set(clientId, new RemoteClient(clientName));
break;
}
case MessageType.CursorPos:
{
const clientId = reader.readUInt();
if (remoteClients.has(clientId)) {
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
remoteClients.get(clientId).rawSetPos(cursorX, cursorY);
}
break;
}
case MessageType.ClientLeft:
{ {
const clientId = reader.readUInt(); const clientId = reader.readUInt();
removeClient(clientId); removeClient(clientId);
@ -577,6 +509,15 @@ kbd {
} }
case MessageType.GroupData: case MessageType.GroupData:
{ {
groupUIBase.style = "";
groupUsers.innerHTML = "";
groupTitle.innerText = reader.readShortString();
const groupUserCount = reader.readUShort();
for (let i = 0; i < groupUserCount; i++) {
const groupUsername = reader.readShortString();
const groupUserLocation = reader.readString();
createGroupUser(groupUsername, groupUserLocation);
}
break; break;
} }
} }
@ -618,6 +559,44 @@ kbd {
const buttons = document.createElement("div"); const buttons = document.createElement("div");
buttons.style.marginTop = "1rem"; buttons.style.marginTop = "1rem";
const disconnectButton = document.createElement("button");
disconnectButton.innerText = localStorage["mpconnectonload"] === "true" ? "Disconnect" : "Connect";
disconnectButton.onclick = () => {
if (localStorage["mpconnectonload"] === "true") {
localStorage["mpconnectonload"] = false;
if (ws) {
ws.close();
}
} else {
localStorage["mpconnectonload"] = true;
doConnect(localStorage["mpapikey"]);
}
disconnectButton.innerText = localStorage["mpconnectonload"] === "true" ? "Disconnect" : "Connect";
};
buttons.appendChild(disconnectButton);
const manageAccountLink = document.createElement("a");
manageAccountLink.style.display = "none";
manageAccountLink.href = "https://multiprobe.eusv.net/";
manageAccountLink.target = "_blank";
buttons.appendChild(manageAccountLink);
const manageAccount = document.createElement("button");
manageAccount.style.marginLeft = "1rem";
manageAccount.innerText = "Manage Account";
manageAccount.onclick = () => {
manageAccountLink.click();
}
buttons.appendChild(manageAccount);
const closeButton = document.createElement("button");
closeButton.innerText = "Close";
closeButton.onclick = () => {
bg.remove();
windowContainer = null;
};
closeButton.style.marginLeft = "1rem";
buttons.appendChild(closeButton);
dialog.appendChild(buttons); dialog.appendChild(buttons);
document.body.appendChild(bg); document.body.appendChild(bg);
@ -677,22 +656,15 @@ kbd {
bg.appendChild(dialog); bg.appendChild(dialog);
const title = document.createElement("h4"); const title = document.createElement("h4");
title.innerText = "MultiProbe"; title.innerText = "MultiProbe";
title.style.marginBottom = ".5rem";
dialog.appendChild(title); dialog.appendChild(title);
/*const submitFunction = (event) => { const manageAccountLink = document.createElement("a");
// Jank manageAccountLink.className = "mplink";
if (event && event.keyCode !== 13) { manageAccountLink.href = "https://multiprobe.eusv.net/";
return; manageAccountLink.innerHTML = "Click here to create an account<br><br>";
} manageAccountLink.target = "_blank";
manageAccountLink.style.marginLeft = manageAccountLink.style.marginRight = ".5rem";
if (username.value.trim() === "") { dialog.appendChild(manageAccountLink);
alert("Username must be valid");
return;
}
localStorage["t00mp_username"] = username.value;
bg.remove();
window.location.href = window.location.href;
};*/
const loginForm = document.createElement("form"); const loginForm = document.createElement("form");
dialog.appendChild(loginForm); dialog.appendChild(loginForm);
const username = document.createElement("input"); const username = document.createElement("input");
@ -700,7 +672,6 @@ kbd {
username.maxLength = 32; username.maxLength = 32;
username.style.width = "12rem"; username.style.width = "12rem";
username.name = "username"; username.name = "username";
//username.onkeypress = submitFunction;
loginForm.appendChild(username); loginForm.appendChild(username);
const password = document.createElement("input"); const password = document.createElement("input");
password.type = "password"; password.type = "password";
@ -715,18 +686,7 @@ kbd {
const submitButton = document.createElement("button"); const submitButton = document.createElement("button");
submitButton.innerText = "Connect"; submitButton.innerText = "Connect";
submitButton.type = "submit"; submitButton.type = "submit";
//submitButton.onclick = () => submitFunction(null);
buttons.appendChild(submitButton); buttons.appendChild(submitButton);
/*if (localStorage["t00mp_username"] !== "") {
const disconnectButton = document.createElement("button");
disconnectButton.innerText = "Disconnect";
disconnectButton.onclick = () => {
localStorage["t00mp_username"] = "";
bg.remove();
window.location.href = window.location.href;
}
buttons.appendChild(disconnectButton);
}*/
const doNotButton = document.createElement("button"); const doNotButton = document.createElement("button");
doNotButton.innerText = "Close"; doNotButton.innerText = "Close";
doNotButton.onclick = () => bg.remove(); doNotButton.onclick = () => bg.remove();
@ -779,7 +739,7 @@ kbd {
openMenuButton.style = "opacity:0.25;position:fixed;top:0px;right:0px;z-index:9999999;margin:4px;background-color:black;color:white;border:1px solid white"; openMenuButton.style = "opacity:0.25;position:fixed;top:0px;right:0px;z-index:9999999;margin:4px;background-color:black;color:white;border:1px solid white";
openMenuButton.innerText = "MultiProbe Menu"; openMenuButton.innerText = "MultiProbe Menu";
openMenuButton.onclick = () => { openMenuButton.onclick = () => {
if (ws) { if (ws || localStorage["mpapikey"]) {
createOnlineDialog(); createOnlineDialog();
} else { } else {
createLoginDialog(); createLoginDialog();
@ -787,11 +747,7 @@ kbd {
}; };
document.body.appendChild(openMenuButton); document.body.appendChild(openMenuButton);
if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "") { if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "" && localStorage["mpconnectonload"] === "true") {
doConnect(localStorage["mpapikey"]); doConnect(localStorage["mpapikey"]);
} }
/*if (username !== "") {
doConnect();
}*/
})(); })();

View file

@ -1,6 +1,6 @@
{ {
"ports": { "ports": {
"http": 38194, "http": 39194,
"ws": 39195 "ws": 39195
}, },
"session": { "session": {

View file

@ -18,8 +18,8 @@ import SessionUser from "./objects/SessionUser";
import PasswordUtility from "./utilities/PasswordUtility"; import PasswordUtility from "./utilities/PasswordUtility";
import CreateEditPartyData from "./interfaces/CreateEditPartyData"; import CreateEditPartyData from "./interfaces/CreateEditPartyData";
import JoinPartyData from "./interfaces/JoinPartyData"; import JoinPartyData from "./interfaces/JoinPartyData";
import UserParty from "./objects/UserParty";
import IdData from "./interfaces/IdData"; import IdData from "./interfaces/IdData";
import Party from "./objects/Party";
Console.customHeader(`MultiProbe server started at ${new Date()}`); Console.customHeader(`MultiProbe server started at ${new Date()}`);
@ -325,6 +325,14 @@ function sendToAll(user:RemoteUser, data:Buffer) {
}); });
} }
function sendToAllInGroup(user:RemoteUser, data:Buffer) {
users.forEach(otherUser => {
if (otherUser.groupId === user.groupId && otherUser.userId !== user.userId) {
otherUser.send(data);
}
});
}
websocketServer.on("connection", (socket) => { websocketServer.on("connection", (socket) => {
const myUUID = crypto.randomUUID(); const myUUID = crypto.randomUUID();
let user:RemoteUser; let user:RemoteUser;
@ -335,9 +343,43 @@ websocketServer.on("connection", (socket) => {
const userLeftPacket = createWriter(Endian.LE, 5).writeByte(MessageType.ClientLeft).writeUInt(user.id).toBuffer(); const userLeftPacket = createWriter(Endian.LE, 5).writeByte(MessageType.ClientLeft).writeUInt(user.id).toBuffer();
users.forEach(otherUser => otherUser.send(userLeftPacket)); users.forEach(otherUser => otherUser.send(userLeftPacket));
sendGroupUpdate(user);
} }
} }
async function sendGroupUpdate(sendUser:RemoteUser, groupSend = false) {
if (!sendUser || user.groupId === Number.MIN_VALUE) {
return;
}
const usersInGroup = new FunkyArray<number, RemoteUser>();
let totalUsernameLength = 0;
await users.forEach(otherUser => {
if (sendUser.groupId === otherUser.groupId && sendUser.userId !== otherUser.userId) {
if (usersInGroup.has(otherUser.userId)) {
totalUsernameLength += otherUser.username.length;
}
usersInGroup.set(otherUser.userId, otherUser);
}
if (!groupSend && sendUser.userId !== otherUser.userId) {
sendGroupUpdate(otherUser, true);
}
});
const writer = createWriter(Endian.LE)
.writeByte(MessageType.GroupData)
.writeShortString(sendUser.groupName)
.writeUShort(usersInGroup.length);
await usersInGroup.forEach(otherUser => {
writer.writeShortString(otherUser.username).writeString(otherUser.rawURL);
});
const groupData = writer.toBuffer();
socket.send(groupData);
}
socket.on("close", closeOrError); socket.on("close", closeOrError);
socket.on("error", closeOrError); socket.on("error", closeOrError);
@ -356,6 +398,11 @@ websocketServer.on("connection", (socket) => {
if (dbUser == null) { if (dbUser == null) {
return; return;
} }
const dbUserParty = await UserService.GetActiveParty(dbUser.Id);
let dbParty: Party | null = null;
if (dbUserParty) {
dbParty = await UserService.GetParty(dbUserParty.PartyId);
}
const rawURL = reader.readString(); const rawURL = reader.readString();
let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", ""); let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", "");
@ -374,9 +421,14 @@ websocketServer.on("connection", (socket) => {
for (const otherUser of usersOnPage) { 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);
} }
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL)); if (dbParty) {
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL, dbUser.Id, dbParty.Id, dbParty.Name));
} else {
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL, dbUser.Id, Number.MIN_VALUE, ""));
}
sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer()); sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer());
user.send(usersToSend.toBuffer()); user.send(usersToSend.toBuffer());
sendGroupUpdate(user);
break; break;
case MessageType.CursorPos: case MessageType.CursorPos:
{ {

View file

@ -12,8 +12,11 @@ export default class RemoteUser {
public cursorY:number = 0; public cursorY:number = 0;
public allowedPings:number; public allowedPings:number;
public lastPingReset:number; public lastPingReset:number;
public userId:number;
public groupId:number;
public groupName:string;
constructor(socket:WebSocket, username:string, currentURL:string, rawURL:string) { constructor(socket:WebSocket, username:string, currentURL:string, rawURL:string, userId:number, groupId:number, groupName:string) {
this.socket = socket; this.socket = socket;
this.id = RemoteUser.USER_IDS++; this.id = RemoteUser.USER_IDS++;
this.username = username; this.username = username;
@ -21,6 +24,9 @@ export default class RemoteUser {
this.rawURL = rawURL; this.rawURL = rawURL;
this.allowedPings = 10; this.allowedPings = 10;
this.lastPingReset = Date.now(); this.lastPingReset = Date.now();
this.userId = userId;
this.groupId = groupId;
this.groupName = groupName;
} }
send(data:Buffer) { send(data:Buffer) {

View file

@ -38,8 +38,8 @@ export default class UserRepo {
public static async insertUpdate(user:User) { public static async insertUpdate(user:User) {
if (user.Id === Number.MIN_VALUE) { if (user.Id === Number.MIN_VALUE) {
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, APIKey, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted) user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
]); ]);
} else { } else {
await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [ await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [