DigDig.IO Server Selector

Server selector for digdig.io. Double click to copy, single click to download.

// ==UserScript==
// @name         DigDig.IO Server Selector
// @namespace    http://tampermonkey.net/
// @version      0.3.3
// @description  Server selector for digdig.io. Double click to copy, single click to download.
// @author       Zertalious (Zert)
// @match        *://digdig.io/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        unsafeWindow
// @grant        GM_addStyle
// ==/UserScript==

unsafeWindow.Image = new Proxy( unsafeWindow.Image, {
	construct( target, thisArgs, args ) {

		const result = Reflect.construct( ...arguments );

		result.crossOrigin = 'anonymous';

		return result;

	}
} );

GM_addStyle( `

body {
	margin: 0;
	overflow: hidden;
	font-family: 'Ubuntu';
	text-shadow: 1px 0 #000, -1px 0 #000, 0 1px #000, 0 -1px #000, 1px 1px #000, -1px -1px #000;
	color: #fff;
	width: 100vw;
	height: 100vh;
}

.pointer-lock canvas {
	pointer-events: none;
}

.pointer-lock:after {
	content: 'Pointer is locked. Press [X] to unlock.';
	position: absolute;
	left: 50%;
	top: 10px;
	transform: translate(-50%, 0);
	background: rgb(233 30 30 / 60%);
	padding: 2px 5px;
	text-shadow: none;
	z-index: 999;
}

.group {
	position: absolute;
	right: 15px;
	bottom: 15px;
	display: flex;
	flex-direction: column;
}

.group > * {
	margin-bottom: 8px;
}

.group > *:last-child {
	margin-bottom: 0;
}

.btn {
	background: #aeaeae;
	font-size: 2.25em;
	text-align: center;
	padding: 0.2em;
	cursor: pointer;
	box-shadow: inset 0 0 0 0.1em rgba(0, 0, 0, 0.25);
	border-radius: 0.1em;
	position: relative;
	user-select: none;
}

.btn:before {
	content: ' ';
	position: absolute;
	top: 0.1em;
	left: 0.1em;
	width: calc(100% - 0.2em);
	height: calc(100% - 0.2em);
	background: transparent;
}

.btn:hover:before {
	background: hsla(0, 0%, 100%, 0.25);
}

.btn:active:before {
	background: rgba(0, 0, 0, 0.1);
}

.btn i {
	text-shadow: none;
}

[tooltip] {
	position: relative;
}

[tooltip]:after {
	content: attr(tooltip);
	font-size: 0.9rem;
	position: absolute;
	right: 100%;
	top: 50%;
	transform: translate(-10px, -50%);
	white-space: nowrap;
	pointer-events: none;
	background: rgba(0, 0, 0, 0.5);
	padding: 0.25em 0.4em;
	border-radius: 0.2em;
	opacity: 0;
	transition: 0.2s;
	z-index: 999;
}

[tooltip]:not(.disabled):hover:after {
	opacity: 1;
}

[tooltip]:is(.force-tooltip):after {
	opacity: 1 !important;
}

.dialog {
	position: absolute;
	right: 85px;
	bottom: 15px;
	background: #aeaeae;
	padding: 0.75em;
	border-radius: 0.3em;
	box-shadow: inset 0 0 0 0.3em rgba(0, 0, 0, 0.25);
	width: 300px;
	transition: 0.2s;
}

.dialog > * {
	margin-bottom: 5px;
}

.dialog > *:last-child {
	margin-bottom: 0;
}

.dialog.disabled {
	transform: translate(0, calc(100% + 15px));
}

.dialog .btn {
	font-size: 1.25rem;
	background: #bb5555;
}

.title {
	font-size: 1.5em;
	text-align: center;
}

.spinner {
	margin: 10px auto;
	width: 60px;
	height: 60px;
	border: 10px solid transparent;
	border-top-color: rgba(0, 0, 0, 0.3);
	border-radius: 50%;
	animation: spin 0.5s infinite;
}

.option {
	background: rgba(0, 0, 0, 0.1);
	padding: 0.5em 0.75em;
	border-radius: 0.25em;
	cursor: pointer;
}

.option.active {
	box-shadow: inset 0 0 0 0.15em rgba(0, 0, 0, 0.2);
}

@keyframes spin {
	from {
		transform: rotate(0);
	}

	to {
		transform: rotate(360deg);
	}
}

` );

const temp = document.createElement( 'div' );

temp.innerHTML += `

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
<div class="group">
	<div class="btn leaderboard-btn" tooltip="Capture leaderboard [G]">
		<i class="fa fa-trophy"></i>
	</div>
	<div class="btn screenshot-btn" tooltip="Take screenshot [F]">
		<i class="fa fa-camera"></i>
	</div>
	<div class="btn servers-btn" tooltip="Servers">
		<i class="fa fa-globe"></i>
	</div>
</div>
<div class="dialog disabled">
	<div class="title">Servers</div>
	<div class="spinner"></div>
	<div class="btn refresh-btn">refresh</div>
	<div class="btn close-btn">close</div>
	</div>
</div>

`;

while ( temp.children.length > 0 ) {

	document.body.appendChild( temp.children[ 0 ] );

}

temp.innerHTML = '';

const spinner = document.querySelector( '.spinner' );

let selectedServerId = new URLSearchParams( document.location.search.substring( 1 ) ).get( 'server' );

if ( selectedServerId ) {

	const idFromUrl = selectedServerId;

	const interval = setInterval( function () {

		if ( idFromUrl !== selectedServerId ) {

			clearInterval( interval );

		}

		try {

			connect( idFromUrl );

			clearInterval( interval );

		} catch ( e ) { }

	}, 50 );

}

fetchAll();

async function fetchAll() {

	spinner.style.display = '';

	await fetchServers( 'ffa' );
	await fetchServers( 'teams' );
	await fetchServers( 'tag' );
	await fetchServers( 'br' );
	await fetchServers( 'maze' );

	spinner.style.display = 'none';

}

const serverIds = {};

const refreshBtn = document.querySelector( '.refresh-btn' );

async function fetchServers( mode ) {

	const response = await fetch( 'https://api.n.m28.io/endpoint/digdig-' + mode + '/findEach' );
	const json = await response.json();

	for ( let key in json.servers ) {

		const id = json.servers[ key ].id;

		if ( ! serverIds[ id ] ) {

			serverIds[ id ] = true;

			const div = document.createElement( 'div' );
			div.classList.add( 'option' );
			div.innerHTML = mode + '_' + key.split( '-' )[ 1 ] + '_' + id;
			div.setAttribute( 'data-id', id );

			dialog.insertBefore( div, refreshBtn );

			div.onclick = function () {

				connect( id );

			}

			if ( selectedServerId === id ) {

				div.click();

			}

		}

	}

}

function connect( id ) {

	selectedServerId = id;

	cp6.forceServerID( selectedServerId );

	onServerChange();

}

unsafeWindow.console.log = new Proxy( unsafeWindow.console.log, {
	apply( target, thisArgs, [ text ] ) {

		if ( typeof text === 'string' && text.startsWith( 'Connecting to ' ) ) {

			const id = text.split('Connecting to ')[ 1 ].split( '.' )[ 0 ];

			if ( id !== selectedServerId ) {

				selectedServerId = id;

				onServerChange();

			}

		}

		return Reflect.apply( ...arguments );

	}
} );

function onServerChange() {

	const url = new URL( window.location );
	url.searchParams.set( 'server', selectedServerId );

	history.pushState( {}, document.title, url );

	const active = document.querySelector( '.option.active' );

	if ( active ) {

		active.classList.remove( 'active' );

	}

	const option = document.querySelector( '[data-id="' + selectedServerId + '"]' );

	if ( option ) {

		option.classList.add( 'active' );

	}

}

const dialog = document.querySelector( '.dialog' );
const serversBtn = document.querySelector( '.servers-btn' );

serversBtn.onclick = function () {

	if ( dialog.classList.contains( 'disabled' ) ) {

		dialog.classList.remove( 'disabled' );
		this.classList.add( 'disabled' );

	} else {

		hideDialog();

	}

}

document.body.onclick = function ( event ) {

	if ( [ 'canvas', 'loading' ].indexOf( event.target.id ) > - 1 || event.target === this ) {

		hideDialog();

	}

}

document.querySelector( '.close-btn' ).onclick = hideDialog;

refreshBtn.onclick = fetchAll;

function hideDialog() {

	dialog.classList.add( 'disabled' );
	serversBtn.classList.remove( 'disabled' );

}

function createClickListener( a, b ) {

	let clicks = 0;

	return function () {

		clicks ++;

		setTimeout( function () {

			if ( clicks === 1 ) {

				a();

			}

			clicks = 0;

		}, 300 );

		if ( clicks === 2 ) {

			b();

		}

	}

}

const screenshotBtn = document.querySelector( '.screenshot-btn' );
const leaderboardBtn = document.querySelector( '.leaderboard-btn' );

const displayCopiedScreenshot = createCopiedDisplayer( screenshotBtn );
const displayCopiedLeaderboard = createCopiedDisplayer( leaderboardBtn );

screenshotBtn.onclick = createClickListener(
	function () {

		downloadCanvas( document.querySelector( 'canvas' ), 'digdig' );

	},
	copyScreenshot
);

leaderboardBtn.onclick = createClickListener(
	function () {

		if ( leaderboard ) {

			downloadCanvas( getLeaderboardCanvas(), 'digdig_leaderboard' );

		}

	},
	copyLeaderboard
);

function copyScreenshot() {

	copyCanvasToClipboard( document.querySelector( 'canvas' ) );

	displayCopiedScreenshot();

}

function copyLeaderboard() {

	if ( leaderboard ) {

		copyCanvasToClipboard( getLeaderboardCanvas() );

		displayCopiedLeaderboard();

	}

}

window.addEventListener( 'keyup', function ( event ) {

	const key = String.fromCharCode( event.keyCode );

	if ( key === 'F' ) {

		copyScreenshot();

	} else if ( key === 'G' ) {

		copyLeaderboard();

	} else if ( key === 'X' ) {

		document.body.classList.toggle( 'pointer-lock' );

	}

} );

function createCopiedDisplayer( element ) {

	let old, timeout;

	return function () {

		if ( element.classList.contains( 'force-tooltip' ) ) {

			clearTimeout( timeout );

		} else {

			old = element.getAttribute( 'tooltip' );
			element.setAttribute( 'tooltip', 'Copied!' );
			element.classList.add( 'force-tooltip' );

		}

		timeout = setTimeout( function () {

			element.setAttribute( 'tooltip', old );
			element.classList.remove( 'force-tooltip' );

		}, 1000 );

	}

}


function getLeaderboardCanvas() {

	const offset = - 115 * 0.8 * 0;

	const canvas = document.createElement( 'canvas' );

	canvas.width = leaderboard.width;
	canvas.height = leaderboard.height + offset;

	canvas.getContext( '2d' ).drawImage( leaderboard, 0, offset );

	return canvas;

}

function copyCanvasToClipboard( canvas ) {

	canvas.toBlob( function ( blob ) {

		navigator.clipboard.write( [ new ClipboardItem( { 'image/png': blob } ) ] );

	} );

}

function downloadCanvas( canvas, filename ) {

	const a = document.createElement( 'a' );

	a.href = canvas.toDataURL();
	a.download = filename + '_' + Date.now() + '.png';

	a.click();

}

let leaderboard;
let leaderboardPartial;

const Canvas = unsafeWindow.OffscreenCanvas ? unsafeWindow.OffscreenCanvas.prototype : unsafeWindow.HTMLCanvasElement.prototype;

Canvas.getContext = new Proxy( Canvas.getContext, {
	apply() {

		const ctx = Reflect.apply( ...arguments );

		ctx.fillText = new Proxy( ctx.fillText, {
			apply( target, thisArgs, args ) {

				if ( args[ 0 ].indexOf( 'Diggers' ) > - 1 ) {

					leaderboardPartial = ctx.canvas;

				}

				return Reflect.apply( ...arguments );

			}
		} );

		ctx.drawImage = new Proxy( ctx.drawImage, {
			apply( target, thisArgs, args ) {

				if ( args[ 0 ] === leaderboardPartial ) {

					leaderboard = ctx.canvas;

				}

				return Reflect.apply( ...arguments );

			}
		} );

		return ctx;

	}
} )