2024-04-18 23:18:49 +01:00
// ==UserScript==
// @name Terminal 00 Multiuser
// @namespace https://*.angusnicneven.com/*
2024-04-20 17:25:41 +01:00
// @version 20240420.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' ;
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 ,
Ping : 6
} ;
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
}
` .split(" \n ").join("").split(" \r ").join("").split(" \t ").join("");
document . head . appendChild ( styles ) ;
const otherCursors = document . createElement ( "div" ) ;
otherCursors . id = "otherCursors" ;
document . body . appendChild ( otherCursors ) ;
2024-04-19 12:03:38 +01:00
2024-04-20 17:20:23 +01:00
let clientWidth = document . body . getBoundingClientRect ( ) . width
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-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 ;
let username = "" ;
if ( localStorage [ "t00mp_username" ] ) {
username = localStorage [ "t00mp_username" ] ;
}
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 = "" ;
}
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 = "" ;
}
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
}
}
}
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 ( ) ;
function doConnect ( ) {
const Buffer = getBufferClass ( ) ;
2024-04-20 17:20:23 +01:00
ws = new WebSocket ( window . location . href . includes ( "//localhost:" ) ? "ws://localhost:38195" : "wss://ws.eusv.net/t00mp" ) ;
2024-04-18 23:18:49 +01:00
let keepAliveInterval ;
ws . onopen = ( ) => {
2024-04-20 17:20:23 +01:00
selfCursor = new RemoteClient ( username ) ;
selfCursor . probeImage . style . visibility = "hidden" ;
2024-04-18 23:18:49 +01:00
const currentPage = window . location . href . split ( "/" ) . slice ( 3 ) . join ( "/" ) ;
ws . send ( createWriter ( Endian . LE , 4 + username . length + currentPage . length ) . writeByte ( MessageType . ClientDetails ) . writeShortString ( username ) . 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 ( ) ;
2024-04-19 00:18:12 +01:00
const clientX = reader . readFloat ( ) ;
const clientY = reader . readInt ( ) ;
remoteClients . set ( clientId , new RemoteClient ( clientName ) ) . rawSetPosInit ( clientX , clientY ) ;
2024-04-18 23:18:49 +01:00
}
ready = true ;
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 ( ) ;
removeClient ( clientId ) ;
}
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-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 ;
setTimeout ( doConnect , 5000 ) ;
}
ws . onclose = onCloseAndError ;
ws . onerror = onCloseAndError ;
}
function createOnlineDialog ( ) {
const bg = document . createElement ( "div" ) ;
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%;width:15rem;height:9rem;background-color:#343434;transform:translate(-50%,-50%);text-align:center;color:white" ;
bg . appendChild ( dialog ) ;
const title = document . createElement ( "h4" ) ;
title . innerText = "T00 MultiUser" ;
dialog . appendChild ( title ) ;
const submitFunction = ( event ) => {
// Jank
if ( event && event . keyCode !== 13 ) {
return ;
}
if ( username . value . trim ( ) === "" ) {
alert ( "Username must be valid" ) ;
return ;
}
localStorage [ "t00mp_username" ] = username . value ;
bg . remove ( ) ;
window . location . href = window . location . href ;
} ;
const username = document . createElement ( "input" ) ;
username . placeholder = "Enter Username" ;
username . maxLength = 32 ;
username . style . width = "12rem" ;
username . onkeypress = submitFunction ;
if ( localStorage [ "t00mp_username" ] ) {
username . value = localStorage [ "t00mp_username" ] ;
}
dialog . appendChild ( username ) ;
const buttons = document . createElement ( "div" ) ;
buttons . style . marginTop = "1rem" ;
dialog . appendChild ( buttons ) ;
const submitButton = document . createElement ( "button" ) ;
2024-04-20 17:20:23 +01:00
submitButton . innerText = ( ! localStorage [ "t00mp_username" ] || localStorage [ "t00mp_username" ] === "" ) ? "Connect" : "Change Username" ;
2024-04-18 23:18:49 +01:00
submitButton . onclick = ( ) => submitFunction ( null ) ;
buttons . appendChild ( submitButton ) ;
2024-04-19 00:18:12 +01:00
if ( localStorage [ "t00mp_username" ] !== "" ) {
const disconnectButton = document . createElement ( "button" ) ;
disconnectButton . innerText = "Disconnect" ;
2024-04-19 10:32:26 +01:00
disconnectButton . onclick = ( ) => {
localStorage [ "t00mp_username" ] = "" ;
bg . remove ( ) ;
window . location . href = window . location . href ;
}
2024-04-19 11:46:36 +01:00
buttons . appendChild ( disconnectButton ) ;
2024-04-19 00:18:12 +01:00
}
2024-04-18 23:18:49 +01:00
const doNotButton = document . createElement ( "button" ) ;
doNotButton . innerText = "Close" ;
doNotButton . onclick = ( ) => bg . remove ( ) ;
doNotButton . style . marginLeft = "1rem" ;
buttons . appendChild ( doNotButton ) ;
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" ;
openMenuButton . innerText = "MultiUser Menu" ;
openMenuButton . onclick = ( ) => createOnlineDialog ( ) ;
document . body . appendChild ( openMenuButton ) ;
if ( username !== "" ) {
doConnect ( ) ;
}
} ) ( ) ;