2024-04-18 23:18:49 +01:00
// ==UserScript==
// @name Terminal 00 Multiuser
// @namespace https://*.angusnicneven.com/*
2024-04-26 03:01:06 +01:00
// @version 20240426.1
2024-04-18 23:18:49 +01:00
// @description Probe with friends!
// @author tgpholly
// @match https://*.angusnicneven.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=angusnicneven.com
// @grant none
// ==/UserScript==
let continueRunningScript = true ;
if ( ! window . TE _ACTIVE ) {
if ( window . location . href . includes ( "www." ) ) {
window . location . href = window . location . href . replace ( "www." , "" ) ;
continueRunningScript = false ;
}
let didWeNukeIt = false ;
function nukeCC ( ) {
if ( didWeNukeIt ) {
return ;
}
try {
const doWeHaveCC = document . getElementById ( "cursor-container" ) ;
if ( doWeHaveCC ) {
doWeHaveCC . remove ( ) ;
didWeNukeIt = true ;
}
} catch ( e ) { }
}
setInterval ( nukeCC , 1000 ) ;
}
( function ( ) {
'use strict' ;
2024-04-26 10:21:27 +01:00
// Make sure to change the userscript version too!!!!!!!!!!
const USERSCRIPT _VERSION _RAW = "20240425.1" ;
const USERSCRIPT _VERSION = parseInt ( USERSCRIPT _VERSION _RAW . replace ( "." , "" ) ) ;
2024-04-26 03:01:06 +01:00
2024-04-18 23:18:49 +01:00
if ( ! continueRunningScript ) {
return ;
}
// This is a minified version of the code at https://git.eusv.net/tgpholly/bufferStuff/releases/tag/1.5.0 + FunkyArray
class BrowserBuffer { constructor ( dataOrSize ) { if ( typeof dataOrSize === "number" ) { this . buffer = new Uint8Array ( dataOrSize ) } else if ( typeof dataOrSize === "object" ) { this . buffer = new Uint8Array ( dataOrSize ) } else { this . buffer = new Uint8Array ( 0 ) } this . dataView = new DataView ( this . buffer . buffer ) } static allocUnsafe ( size ) { return this . alloc ( size ) } static allocUnsafeSlow ( size ) { return this . alloc ( size ) } static alloc ( size ) { return new BrowserBuffer ( size ) } static concat ( buffers , totalLength ) { let joinedArrays ; if ( totalLength !== undefined ) { joinedArrays = new Uint8Array ( totalLength ) } else { let arraysLength = 0 ; for ( const buffer of buffers ) { arraysLength += buffer . length } joinedArrays = new Uint8Array ( arraysLength ) } let offset = 0 ; for ( const buffer of buffers ) { joinedArrays . set ( buffer . buffer , offset ) ; offset += buffer . length } return new BrowserBuffer ( joinedArrays ) } static from ( data ) { if ( typeof data === "string" ) { throw new Error ( "BrowserBuffer does not currently support creating buffers from strings." ) } return new BrowserBuffer ( data ) } static of ( ) { } static isBuffer ( ) { } static isEncoding ( ) { } static byteLength ( ) { } static copyBytesFrom ( ) { } static compare ( ) { } get length ( ) { return this . buffer . length } checkRanged ( value , valueName , lowRange , highRange ) { if ( value < lowRange || value > highRange ) { throw new Error ( ` The value of " ${ valueName } " is out of range. It must be >= ${ lowRange } and <= ${ highRange } . Received ${ value } ` ) } } checkValue ( value , low , high ) { this . checkRanged ( value , "value" , low , high ) } checkOffset ( offset ) { if ( offset ) { this . checkRanged ( offset , "offset" , 0 , this . buffer . length - 1 ) } } writeInt8 ( value , offset ) { this . checkValue ( value , - 128 , 127 ) ; this . checkOffset ( offset ) ; this . dataView . setInt8 ( offset , value ) ; return this } writeUInt8 ( value , offset ) { this . checkValue ( value , 0 , 255 ) ; this . checkOffset ( offset ) ; this . dataView . setUint8 ( offset , value ) ; return this } writeInt16LE ( value , offset ) { this . checkValue ( value , - 32768 , 32767 ) ; this . checkOffset ( offset ) ; this . dataView . setInt16 ( offset , value , true ) ; return this } writeUInt16LE ( value , offset ) { this . checkValue ( value , 0 , 65535 ) ; this . checkOffset ( offset ) ; this . dataView . setUint16 ( offset , value , true ) ; return this } writeInt32LE ( value , offset ) { this . checkValue ( value , - 2147483648 , 2147483647 ) ; this . checkOffset ( offset ) ; this . dataView . setInt32 ( offset , value , true ) ; return this } writeUInt32LE ( value , offset ) { this . checkValue ( value , 0 , 4294967295 ) ; this . checkOffset ( offset ) ; this . dataView . setUint32 ( offset , value , true ) ; return this } writeBigInt64LE ( value , offset ) { if ( typeof value === "number" ) { value = BigInt ( value ) } this . checkValue ( value , - 9223372036854775808 n , 9223372036854775808 n ) ; this . checkOffset ( offset ) ; this . dataView . setBigInt64 ( offset , value , true ) ; return this } writeBigUint64LE ( value , offset ) { if ( typeof value === "number" ) { value = BigInt ( value ) } this . checkValue ( value , 0 n , 18446744073709551616 n ) ; this . checkOffset ( offset ) ; this . dataView . setBigUint64 ( offset , value , true ) ; return this } writeFloatLE ( value , offset ) { this . checkOffset ( offset ) ; this . dataView . setFloat32 ( offset , value , true ) ; return this } writeDoubleLE ( value , offset ) { this . checkOffset ( offset ) ; this . dataView . setFloat64 ( offset , value , true ) ; return this } writeInt16BE ( value , offset ) { this . checkValue ( value , - 32768 , 32767 ) ; this . checkOffset ( offset ) ; this . dataView . setInt16 ( offset , value , false ) ; return this } writeUInt16BE ( value , offset ) { this . checkValue ( value , 0 , 65535 ) ; this . checkOffset ( offset ) ; this . dataView . setUint16 ( offset , value , false ) ; return this } writeInt32BE ( value , offset ) { this . checkValue ( value , - 2147483648 , 2147483647 ) ; this . checkOffset ( offset ) ; this . dataView . setInt32 ( offset , value , false ) ; return this } writeUInt32BE ( value , offset ) { this . checkValue ( value , 0 , 4294967295 ) ; this . checkOffset ( offset ) ; this . dataView . setUint32 ( offset , value , false ) ; return this } writeBigInt64BE ( value , offset ) { if ( typeof value === "number" ) { value = BigInt ( value ) } this . checkValue ( value , - ( 2 n * * 63 n ) , 2 n * * 63 n ) ; this . checkOffset ( offset ) ; this . dataView . setBigInt64 ( offset , value , false ) ; return this } writeBigUint64BE ( value , offset ) { if ( typeof value === "number" ) { value = BigInt ( value ) } this . checkValue ( value , 0 n , 2 n * * 64 n ) ; this . checkOffset ( offset ) ; this . dataView . setBigUint64 ( offset , value , false ) ; return this } writeFloatBE ( value , offset ) { this . checkOffset ( offset ) ; this . dataView . setFloat32 ( offset , value , false ) ; return this } writeDouble
const MessageType = {
KeepAlive : 0 ,
ClientDetails : 1 ,
CursorPos : 2 ,
ClientJoined : 3 ,
Clients : 4 ,
ClientLeft : 5 ,
2024-04-25 02:37:37 +01:00
Ping : 6 ,
GroupData : 7
2024-04-18 23:18:49 +01:00
} ;
const styles = document . createElement ( "style" ) ;
styles . innerHTML = `
# otherCursors {
position : absolute ;
top : 0 px ;
left : 0 px ;
width : 100 % ;
height : 100 % ;
z - index : 999999999 ;
pointer - events : none ;
overflow : hidden ;
}
@ keyframes ping {
0 % {
scale : 0 ;
opacity : 1 ;
}
100 % {
scale : 2 ;
opacity : 0 ;
}
}
. ping {
opacity : 0 ;
position : absolute ;
animation : ping 2 s linear ;
animation - iteration - count : 1 ;
image - rendering : pixelated ;
background : url ( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAXVJREFUeJztm8ESwyAIRLX//8/2EjKKmhhFUeDN9NbK7kIybaPerSU0vs9PVbGwUGI4hDb/3meypumcsfDtEhsuGCsv8Pw5Us2UiwXnUvEDnaxOTrQmiXaKRRLjk7qVTRVVEKMCA7WglppX4bjusptmLCJc7uF1pIae5HDXl6ePWKqHu+s18DTMKTK7AAHTNJ5gHiDXepJ5gEzzieaBYe0nmwdePfwWijkKCd0HPnuRZB6oerJLACGx+0DRm01AhOTuA5lH9ROgPgBAw/gDiVf1E2ABcAvYAU3XP3B7Vj8BFgC3AG4sAG4B3FgA3AK4sQC4BeyCpm+D9mswxgLgFrATGu4D9qcoRn0AGMmXgT0ZakXiFNjT4a9ImoJuLxJCsD1Co5w8Bar3CqreLap6v/DSHeM7BdG9Xd4OTPQWdfzTwHZkJhGB0qdY87XmVTiu213Tjs2NfBiRBOGcnoOTmKxbdzHhR2dLVDv5xOmHp59ovVMv0/UHCfuPGCq5foYAAAAASUVORK5CYII=" ) ;
width : 64 px ;
height : 64 px ;
2024-04-20 17:25:16 +01:00
z - index : - 1 ! important ;
2024-04-18 23:18:49 +01:00
}
2024-04-24 00:06:42 +01:00
. grouphidden {
right : - 12 rem ! important ;
}
. groupui {
position : fixed ;
top : 2 rem ;
right : 0 px ;
height : 25 rem ;
width : 12 rem ;
background - color : black ;
color : white ;
z - index : 9999998 ;
}
. groupui - popper {
position : absolute ;
top : 0 px ;
left : - 1 rem ;
width : 1 rem ;
height : 100 % ;
opacity : 0.25 ;
background - color : black ;
border : 1 px solid white ;
color : white ;
}
. groupui - title {
position : absolute ;
top : 0 px ;
left : 0 px ;
width : 100 % ;
text - align : center ;
height : 2 rem ;
line - height : 2 rem ;
background - color : rgba ( 255 , 255 , 255 , 0.1 ) ;
}
. groupui - scrollbox {
position : absolute ;
top : 2 rem ;
left : 0 px ;
width : 100 % ;
height : calc ( 100 % - 2 rem ) ;
overflow - y : scroll ;
}
. groupui - user {
position : relative ;
width : 100 % ;
height : 2 rem ;
background - color : rgba ( 255 , 255 , 255 , 0.2 ) ;
}
. groupui - user : nth - child ( 2 n ) {
background - color : rgba ( 255 , 255 , 255 , 0.15 ) ;
}
. groupui - user p {
position : absolute ;
line - height : 2 rem ;
margin : 0 ;
margin - left : . 5 rem ;
}
. groupui - user . buttons {
position : absolute ;
height : 100 % ;
top : 0 px ;
right : 0 px ;
margin - right : . 5 rem ;
}
. groupui - user . buttons button {
margin - left : . 5 rem ;
margin - top : . 2 rem ;
}
2024-04-25 10:12:17 +01:00
kbd {
background - color : # eee ;
border - radius : 3 px ;
border : 1 px solid # b4b4b4 ;
box - shadow : 0 1 px 1 px rgba ( 0 , 0 , 0 , 0.2 ) , 0 2 px 0 0 rgba ( 255 , 255 , 255 , 0.7 ) inset ;
color : # 333 ;
display : inline - block ;
font - size : 0.85 em ;
font - weight : 700 ;
line - height : 1 ;
padding : 2 px 4 px ;
white - space : nowrap ;
}
2024-04-26 03:01:06 +01:00
. mplink , . mplink : visited {
color : # d2738a ;
}
. mplink : hover {
color : # c1b492 ;
}
2024-04-18 23:18:49 +01:00
` .split(" \n ").join("").split(" \r ").join("").split(" \t ").join("");
document . head . appendChild ( styles ) ;
2024-04-26 03:01:06 +01:00
if ( ! localStorage [ "mpconnectonload" ] ) {
localStorage [ "mpconnectonload" ] = true ;
}
2024-04-18 23:18:49 +01:00
const otherCursors = document . createElement ( "div" ) ;
otherCursors . id = "otherCursors" ;
document . body . appendChild ( otherCursors ) ;
2024-04-19 12:03:38 +01:00
2024-04-24 00:06:42 +01:00
const groupUIBase = document . createElement ( "div" ) ;
groupUIBase . style = "display:none" ;
groupUIBase . classList . add ( "groupui" ) ;
groupUIBase . classList . add ( "grouphidden" ) ;
const groupPopper = document . createElement ( "button" ) ;
groupPopper . classList . add ( "groupui-popper" ) ;
groupPopper . onclick = ( ) => {
groupUIBase . classList . toggle ( "grouphidden" ) ;
groupPopper . innerText = groupUIBase . classList . contains ( "grouphidden" ) ? "<" : ">" ;
} ;
groupPopper . innerText = "<" ;
groupUIBase . appendChild ( groupPopper ) ;
const groupTitle = document . createElement ( "div" ) ;
groupTitle . classList . add ( "groupui-title" ) ;
groupTitle . innerText = "GROUP_NAME" ;
groupUIBase . appendChild ( groupTitle ) ;
const groupUsers = document . createElement ( "div" ) ;
groupUsers . classList . add ( "groupui-scrollbox" ) ;
groupUIBase . appendChild ( groupUsers ) ;
document . body . appendChild ( groupUIBase ) ;
function createGroupUser ( username , location ) {
const user = document . createElement ( "div" ) ;
user . classList . add ( "groupui-user" ) ;
const usernameBox = document . createElement ( "p" ) ;
usernameBox . innerText = username ;
user . appendChild ( usernameBox ) ;
const buttonBox = document . createElement ( "div" ) ;
buttonBox . classList . add ( "buttons" ) ;
user . appendChild ( buttonBox ) ;
2024-04-26 03:01:06 +01:00
/ * c o n s t f o l l o w B u t t o n = d o c u m e n t . c r e a t e E l e m e n t ( " b u t t o n " ) ;
2024-04-24 00:06:42 +01:00
followButton . innerText = "F" ;
2024-04-26 03:01:06 +01:00
buttonBox . appendChild ( followButton ) ; * /
2024-04-24 00:06:42 +01:00
const gotoButton = document . createElement ( "button" ) ;
2024-04-26 03:01:06 +01:00
gotoButton . innerText = "Go To" ;
2024-04-25 02:37:37 +01:00
gotoButton . onclick = ( ) => window . location . href = location ;
2024-04-24 00:06:42 +01:00
buttonBox . appendChild ( gotoButton ) ;
groupUsers . appendChild ( user ) ;
}
2024-04-25 02:37:37 +01:00
let clientWidth = document . body . getBoundingClientRect ( ) . width ;
2024-04-20 17:20:23 +01:00
2024-04-19 12:03:38 +01:00
setInterval ( ( ) => {
2024-04-20 17:20:23 +01:00
if ( document . body . scrollHeight > window . innerHeight ) {
otherCursors . style = ` width: ${ clientWidth = document . body . getBoundingClientRect ( ) . width } px;height: ${ document . body . scrollHeight } px ` ;
} else {
otherCursors . style = ` width: ${ clientWidth = document . body . getBoundingClientRect ( ) . width } px;height: ${ window . innerHeight } px ` ;
}
2024-04-19 12:03:38 +01:00
} , 1000 ) ;
2024-04-26 03:01:06 +01:00
2024-04-26 10:38:46 +01:00
const versionFetchAddress = window . location . href . replace ( "127.0.0.1" , "localhost" ) . includes ( "//localhost:" ) ? "http://localhost:38194/api/version" : "https://multiprobe.eusv.net/api/version" ;
fetch ( versionFetchAddress , { method : "post" } ) . then ( response => {
2024-04-26 10:21:27 +01:00
console . log ( "hi" ) ;
response . text ( ) . then ( versionNumberRaw => {
const versionNumber = parseInt ( versionNumberRaw ) ;
if ( versionNumber > USERSCRIPT _VERSION ) {
// We're out of date >:(
createUpdateDialog ( ` ${ versionNumberRaw . slice ( 0 , versionNumberRaw . length - 1 ) } . ${ versionNumberRaw . slice ( - 1 ) } ` ) ;
2024-04-26 03:01:06 +01:00
}
} ) ;
2024-04-26 10:21:27 +01:00
} ) ;
2024-04-18 23:18:49 +01:00
const keepAlivePacket = createWriter ( Endian . LE , 1 ) . writeByte ( MessageType . KeepAlive ) . toBuffer ( ) ;
let remoteClients = new FunkyArray ( ) ;
let ws ;
let ready = false ;
let currentMouseX = 0 ;
let currentMouseY = 0 ;
let oldMouseX = 0 ;
let oldMouseY = 0 ;
let lastSendTime = 0 ;
class RemoteClient {
constructor ( name ) {
this . name = name ;
this . element = document . createElement ( "div" ) ;
this . element . style . position = "absolute" ;
this . element . style . transform = ` translate(-50%, -50%) ` ;
const image = new Image ( ) ;
let cursorImage = window . getComputedStyle ( document . body ) . cursor . replace ( "url(" , "" ) . split ( '"' ) . join ( "" ) . split ( "'" ) . join ( "" ) . split ( ")" ) [ 0 ] ;
if ( cursorImage === "auto" ) {
cursorImage = "https://angusnicneven.com/cursor/rrw.png" ;
}
image . src = cursorImage ;
2024-04-20 17:20:23 +01:00
this . probeImage = image ;
2024-04-18 23:18:49 +01:00
this . element . appendChild ( image ) ;
const clientName = document . createElement ( "div" ) ;
2024-04-20 17:20:23 +01:00
clientName . style = "position:absolute;left:100%;top:100%;background-color:black;padding:4px 8px;color:white;font-size:12px;font-family:Arial,sans-serif;" ;
2024-04-18 23:18:49 +01:00
clientName . innerText = name ;
this . element . appendChild ( clientName ) ;
otherCursors . appendChild ( this . element ) ;
this . targetX = 0 ;
this . targetY = 0 ;
this . actualX = 0 ;
this . actualY = 0 ;
this . oldActualX = 0 ;
this . oldActualY = 0 ;
2024-04-20 17:20:23 +01:00
this . hasBeenMoved = false ;
this . element . visibility = "hidden" ;
2024-04-18 23:18:49 +01:00
}
rawSetPos ( x , y ) {
2024-04-20 17:20:23 +01:00
if ( ! this . hasBeenMoved ) {
this . element . visibility = "" ;
2024-04-20 17:34:32 +01:00
this . hasBeenMoved = true ;
2024-04-20 17:20:23 +01:00
}
this . targetX = Math . round ( x * clientWidth ) ;
2024-04-18 23:18:49 +01:00
this . targetY = y ;
}
2024-04-19 00:18:12 +01:00
rawSetPosInit ( x , y ) {
2024-04-20 17:20:23 +01:00
if ( ! this . hasBeenMoved ) {
this . element . visibility = "" ;
2024-04-20 17:34:32 +01:00
this . hasBeenMoved = true ;
2024-04-20 17:20:23 +01:00
}
this . actualX = this . targetX = Math . round ( x * clientWidth ) ;
2024-04-19 00:18:12 +01:00
this . actualY = this . targetY = y ;
}
2024-04-18 23:18:49 +01:00
updateCursor ( ) {
const x = Math . round ( this . actualX ) ;
const y = Math . round ( this . actualY ) ;
if ( y !== this . oldActualY ) {
this . element . style . top = ` ${ y } px ` ;
this . oldActualY = y ;
}
if ( x !== this . oldActualX ) {
this . element . style . left = ` ${ x } px ` ;
this . oldActualX = x ;
}
}
}
2024-04-20 17:20:23 +01:00
let selfCursor ;
2024-04-18 23:18:49 +01:00
function createPing ( x , y ) {
const pingDiv = document . createElement ( "div" ) ;
pingDiv . className = "ping" ;
pingDiv . style . top = ` ${ y } px ` ;
pingDiv . style . left = ` ${ x } px ` ;
otherCursors . appendChild ( pingDiv ) ;
2024-04-19 09:44:26 +01:00
setTimeout ( ( ) => pingDiv . remove ( ) , 2000 ) ;
2024-04-18 23:18:49 +01:00
}
function removeClient ( id ) {
if ( remoteClients . has ( id ) ) {
const client = remoteClients . get ( id ) ;
remoteClients . remove ( id ) ;
client . element . remove ( ) ;
}
}
let rawMouseX = 0 ;
let rawMouseY = 0 ;
window . onmousemove = ( e ) => {
currentMouseX = ( rawMouseX = e . clientX ) + document . body . scrollLeft ;
currentMouseY = ( rawMouseY = e . clientY ) + document . body . scrollTop ;
2024-04-20 17:20:23 +01:00
if ( selfCursor ) {
selfCursor . rawSetPosInit ( currentMouseX / clientWidth , currentMouseY ) ;
selfCursor . updateCursor ( ) ;
}
2024-04-18 23:18:49 +01:00
}
window . onscroll = ( ) => {
currentMouseX = rawMouseX + document . body . scrollLeft ;
currentMouseY = rawMouseY + document . body . scrollTop ;
2024-04-20 17:20:23 +01:00
if ( selfCursor ) {
selfCursor . rawSetPosInit ( currentMouseX / clientWidth , currentMouseY ) ;
selfCursor . updateCursor ( ) ;
}
2024-04-18 23:18:49 +01:00
}
2024-04-20 17:20:23 +01:00
let allowedPings = 10 ;
2024-04-18 23:18:49 +01:00
window . onkeypress = ( e ) => {
if ( e . key === "p" ) {
if ( ws && ready ) {
2024-04-20 17:20:23 +01:00
if ( allowedPings > 0 ) {
allowedPings -- ;
ws . send ( createWriter ( Endian . LE , 9 ) . writeByte ( MessageType . Ping ) . writeFloat ( ( rawMouseX + document . body . scrollLeft - 32 ) / clientWidth ) . writeInt ( rawMouseY + document . body . scrollTop - 32 ) . toBuffer ( ) ) ;
}
2024-04-18 23:18:49 +01:00
}
2024-04-20 17:34:32 +01:00
} else if ( e . key === "n" ) {
if ( ws && ready && selfCursor ) {
localStorage [ "t00mp_cursorStyle" ] = selfCursor . element . style . visibility = selfCursor . element . style . visibility === "hidden" ? "" : "hidden" ;
}
2024-04-18 23:18:49 +01:00
}
}
function lerp ( value1 , value2 , amount ) {
return value1 + ( value2 - value1 ) * amount ;
}
let timeLastFrame = performance . now ( ) ;
let frameDeltaTime = 0 ;
2024-04-20 17:20:23 +01:00
let timeSinceLastPingReset = performance . now ( ) ;
2024-04-18 23:18:49 +01:00
function animate ( ) {
frameDeltaTime = ( performance . now ( ) - timeLastFrame ) * 0.001 ;
if ( ws && ready ) {
if ( currentMouseX !== oldMouseX || currentMouseY !== oldMouseY ) {
if ( ( performance . now ( ) - lastSendTime ) >= 41 ) {
lastSendTime = performance . now ( ) ;
oldMouseX = currentMouseX ;
oldMouseY = currentMouseY ;
2024-04-20 17:20:23 +01:00
ws . send ( createWriter ( Endian . LE , 9 ) . writeByte ( MessageType . CursorPos ) . writeFloat ( oldMouseX / clientWidth ) . writeInt ( currentMouseY ) . toBuffer ( ) ) ;
2024-04-18 23:18:49 +01:00
}
}
}
2024-04-20 17:20:23 +01:00
if ( ( performance . now ( ) - timeSinceLastPingReset ) >= 1000 ) {
allowedPings = 10 ;
timeSinceLastPingReset = performance . now ( ) ;
}
2024-04-18 23:18:49 +01:00
remoteClients . forEach ( remoteUser => {
remoteUser . actualX = lerp ( remoteUser . actualX , remoteUser . targetX , 20 * frameDeltaTime ) ;
remoteUser . actualY = lerp ( remoteUser . actualY , remoteUser . targetY , 20 * frameDeltaTime ) ;
remoteUser . updateCursor ( ) ;
} ) ;
requestAnimationFrame ( animate ) ;
timeLastFrame = performance . now ( ) ;
}
animate ( ) ;
2024-04-25 10:12:17 +01:00
let windowContainer = null ;
function doConnect ( apiKey ) {
const Buffer = getBufferClass ( ) ;
2024-04-26 10:38:46 +01:00
ws = new WebSocket ( window . location . href . includes ( "//localhost:" ) ? "ws://localhost:38195" : "wss://ws.eusv.net/t00mp" ) ;
2024-04-25 10:12:17 +01:00
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 ;
}
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 :
2024-04-18 23:18:49 +01:00
{
const clientId = reader . readUInt ( ) ;
removeClient ( clientId ) ;
2024-04-25 10:12:17 +01:00
break ;
2024-04-18 23:18:49 +01:00
}
case MessageType . Ping :
{
const cursorX = reader . readFloat ( ) ;
const cursorY = reader . readInt ( ) ;
2024-04-20 17:20:23 +01:00
createPing ( cursorX * clientWidth , cursorY ) ;
2024-04-25 10:12:17 +01:00
break ;
}
case MessageType . GroupData :
{
2024-04-26 03:01:06 +01:00
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 ) ;
}
2024-04-25 10:12:17 +01:00
break ;
2024-04-18 23:18:49 +01:00
}
}
}
ws . onmessage = ( e ) => {
e . data . arrayBuffer ( ) . then ( onMessage ) ;
}
function onCloseAndError ( ) {
if ( keepAliveInterval ) {
clearInterval ( keepAliveInterval ) ;
keepAliveInterval = undefined ;
}
ws = undefined ;
ready = false ;
2024-04-25 02:37:37 +01:00
setTimeout ( ( ) => doConnect ( localStorage [ "mpapikey" ] ) , 5000 ) ;
2024-04-18 23:18:49 +01:00
}
ws . onclose = onCloseAndError ;
ws . onerror = onCloseAndError ;
}
2024-04-25 10:12:17 +01:00
function createOnlineDialog ( ) {
const bg = document . createElement ( "div" ) ;
windowContainer = bg ;
bg . style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)" ;
const dialog = document . createElement ( "div" ) ;
dialog . style = "position:absolute;top:50%;left:50%;min-width:15rem;background-color:#343434;padding:1rem;transform:translate(-50%,-50%);text-align:center;color:white" ;
bg . appendChild ( dialog ) ;
const title = document . createElement ( "h4" ) ;
title . innerText = "MultiProbe" ;
title . style . marginBottom = "0px" ;
dialog . appendChild ( title ) ;
const subtitle = document . createElement ( "h5" ) ;
subtitle . innerText = ` Logged in as ${ localStorage [ "t00mp_username" ] } ` ;
subtitle . style . marginTop = ".5rem" ;
dialog . appendChild ( subtitle ) ;
const buttons = document . createElement ( "div" ) ;
buttons . style . marginTop = "1rem" ;
2024-04-26 03:01:06 +01:00
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 ) ;
2024-04-25 10:12:17 +01:00
dialog . appendChild ( buttons ) ;
document . body . appendChild ( bg ) ;
}
2024-04-26 10:21:27 +01:00
function createUpdateDialog ( versionNumber ) {
const bg = document . createElement ( "div" ) ;
windowContainer = bg ;
bg . style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)" ;
const dialog = document . createElement ( "div" ) ;
dialog . style = "position:absolute;top:50%;left:50%;min-width:15rem;background-color:#343434;padding:1rem;transform:translate(-50%,-50%);text-align:center;color:white" ;
bg . appendChild ( dialog ) ;
const title = document . createElement ( "h4" ) ;
title . innerText = "An update is available!" ;
title . style . marginBottom = "0px" ;
dialog . appendChild ( title ) ;
const updateText = document . createElement ( "p" ) ;
updateText . innerHTML = ` <a class="mplink" href="https://git.eusv.net/tgpholly/t00-multiuser/raw/branch/master/client/Terminal-00-Multiuser.user.js? ${ Date . now ( ) } ">Please click here to update to the latest version.</a><br>This takes less than a few seconds to do and ensures you can continue to use MultiProbe.<br><br>Current Version: ${ USERSCRIPT _VERSION _RAW } <br>New Version: ${ versionNumber } ` ;
dialog . appendChild ( updateText ) ;
const buttons = document . createElement ( "div" ) ;
buttons . style . marginTop = "1rem" ;
const gotIt = document . createElement ( "button" ) ;
gotIt . onclick = ( ) => {
bg . remove ( ) ;
}
gotIt . innerText = "Later" ;
buttons . appendChild ( gotIt ) ;
dialog . appendChild ( buttons ) ;
document . body . appendChild ( bg ) ;
}
2024-04-25 10:12:17 +01:00
function createFirstTimeDialog ( ) {
if ( localStorage [ "mpshowfirsttime" ] && localStorage [ "mpshowfirsttime" ] === "false" ) {
return ;
}
const bg = document . createElement ( "div" ) ;
windowContainer = bg ;
bg . style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)" ;
const dialog = document . createElement ( "div" ) ;
dialog . style = "position:absolute;top:50%;left:50%;min-width:15rem;background-color:#343434;padding:1rem;transform:translate(-50%,-50%);text-align:center;color:white" ;
bg . appendChild ( dialog ) ;
const title = document . createElement ( "h4" ) ;
title . innerText = "Welcome to MultiProbe!" ;
title . style . marginBottom = "0px" ;
dialog . appendChild ( title ) ;
const actionList = document . createElement ( "ul" ) ;
actionList . style . textAlign = "start" ;
dialog . appendChild ( actionList ) ;
const pingLI = document . createElement ( "li" ) ;
pingLI . innerHTML = "You can create a \"ping\" at your cursor position by pressing <kbd>P</kbd>" ;
actionList . appendChild ( pingLI ) ;
const nameLI = document . createElement ( "li" ) ;
nameLI . innerHTML = "You can show/hide your own name for screenshots by pressing <kbd>N</kbd>" ;
actionList . appendChild ( nameLI ) ;
const buttons = document . createElement ( "div" ) ;
buttons . style . marginTop = "1rem" ;
const gotIt = document . createElement ( "button" ) ;
gotIt . onclick = ( ) => {
localStorage [ "mpshowfirsttime" ] = "false" ;
bg . remove ( ) ;
windowContainer = null ;
}
gotIt . innerText = "Got It" ;
buttons . appendChild ( gotIt ) ;
dialog . appendChild ( buttons ) ;
document . body . appendChild ( bg ) ;
}
2024-04-25 02:37:37 +01:00
function createLoginDialog ( ) {
2024-04-18 23:18:49 +01:00
const bg = document . createElement ( "div" ) ;
2024-04-25 10:12:17 +01:00
windowContainer = bg ;
2024-04-18 23:18:49 +01:00
bg . style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)" ;
const dialog = document . createElement ( "div" ) ;
2024-04-25 02:37:37 +01:00
dialog . style = "position:absolute;top:50%;left:50%;width:15rem;background-color:#343434;padding-bottom:1rem;transform:translate(-50%,-50%);text-align:center;color:white" ;
2024-04-18 23:18:49 +01:00
bg . appendChild ( dialog ) ;
const title = document . createElement ( "h4" ) ;
2024-04-25 02:37:37 +01:00
title . innerText = "MultiProbe" ;
2024-04-26 03:01:06 +01:00
title . style . marginBottom = ".5rem" ;
2024-04-18 23:18:49 +01:00
dialog . appendChild ( title ) ;
2024-04-26 03:01:06 +01:00
const manageAccountLink = document . createElement ( "a" ) ;
manageAccountLink . className = "mplink" ;
manageAccountLink . href = "https://multiprobe.eusv.net/" ;
manageAccountLink . innerHTML = "Click here to create an account<br><br>" ;
manageAccountLink . target = "_blank" ;
manageAccountLink . style . marginLeft = manageAccountLink . style . marginRight = ".5rem" ;
dialog . appendChild ( manageAccountLink ) ;
2024-04-25 02:37:37 +01:00
const loginForm = document . createElement ( "form" ) ;
dialog . appendChild ( loginForm ) ;
2024-04-18 23:18:49 +01:00
const username = document . createElement ( "input" ) ;
username . placeholder = "Enter Username" ;
username . maxLength = 32 ;
username . style . width = "12rem" ;
2024-04-25 02:37:37 +01:00
username . name = "username" ;
loginForm . appendChild ( username ) ;
const password = document . createElement ( "input" ) ;
password . type = "password" ;
password . style . marginTop = ".5rem" ;
password . placeholder = "Enter Password" ;
password . style . width = "12rem" ;
password . name = "password" ;
loginForm . appendChild ( password ) ;
2024-04-18 23:18:49 +01:00
const buttons = document . createElement ( "div" ) ;
buttons . style . marginTop = "1rem" ;
2024-04-25 02:37:37 +01:00
loginForm . appendChild ( buttons ) ;
2024-04-18 23:18:49 +01:00
const submitButton = document . createElement ( "button" ) ;
2024-04-25 02:37:37 +01:00
submitButton . innerText = "Connect" ;
submitButton . type = "submit" ;
2024-04-18 23:18:49 +01:00
buttons . appendChild ( submitButton ) ;
const doNotButton = document . createElement ( "button" ) ;
doNotButton . innerText = "Close" ;
doNotButton . onclick = ( ) => bg . remove ( ) ;
doNotButton . style . marginLeft = "1rem" ;
buttons . appendChild ( doNotButton ) ;
2024-04-25 02:37:37 +01:00
loginForm . onsubmit = ( e ) => {
e . preventDefault ( ) ;
username . disabled = true ;
password . disabled = true ;
submitButton . disabled = true ;
doNotButton . disabled = true ;
2024-04-25 10:12:17 +01:00
title . innerText = "Authenticating..." ;
2024-04-26 10:38:46 +01:00
fetch ( window . location . href . replace ( "127.0.0.1" , "localhost" ) . includes ( "//localhost:" ) ? "http://localhost:38194/api/login" : "https://multiprobe.eusv.net/api/login" , {
2024-04-25 02:37:37 +01:00
method : "POST" ,
body : ` username= ${ encodeURIComponent ( username . value ) } &password= ${ encodeURIComponent ( password . value ) } ` ,
headers : {
"Content-Type" : "application/x-www-form-urlencoded"
}
} ) . then ( res => {
if ( res . status === 200 ) {
res . text ( ) . then ( apiKey => {
2024-04-25 10:12:17 +01:00
title . innerText = "Connecting to realtime server..." ;
2024-04-25 02:37:37 +01:00
localStorage [ "t00mp_username" ] = username . value ;
localStorage [ "mpapikey" ] = apiKey ;
doConnect ( apiKey ) ;
} ) ;
} else {
2024-04-25 10:12:17 +01:00
title . innerText = "Incorrect Login" ;
2024-04-25 02:37:37 +01:00
username . disabled = false ;
password . disabled = false ;
submitButton . disabled = false ;
doNotButton . disabled = false ;
}
} ) . catch ( err => {
console . error ( err ) ;
username . disabled = false ;
password . disabled = false ;
submitButton . disabled = false ;
doNotButton . disabled = false ;
} ) ;
}
2024-04-18 23:18:49 +01:00
document . body . appendChild ( bg ) ;
}
const openMenuButton = document . createElement ( "button" ) ;
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" ;
2024-04-25 02:37:37 +01:00
openMenuButton . innerText = "MultiProbe Menu" ;
2024-04-25 10:12:17 +01:00
openMenuButton . onclick = ( ) => {
2024-04-26 03:01:06 +01:00
if ( ws || localStorage [ "mpapikey" ] ) {
2024-04-25 10:12:17 +01:00
createOnlineDialog ( ) ;
} else {
createLoginDialog ( ) ;
}
} ;
2024-04-18 23:18:49 +01:00
document . body . appendChild ( openMenuButton ) ;
2024-04-26 03:01:06 +01:00
if ( localStorage [ "mpapikey" ] && localStorage [ "mpapikey" ] !== "" && localStorage [ "mpconnectonload" ] === "true" ) {
2024-04-25 02:37:37 +01:00
doConnect ( localStorage [ "mpapikey" ] ) ;
2024-04-18 23:18:49 +01:00
}
} ) ( ) ;