// ==UserScript==
// @name DigDig.IO Server Selector
// @namespace http://tampermonkey.net/
// @version 0.1.8
// @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==
const sockets = [];
unsafeWindow.WebSocket = new Proxy( unsafeWindow.WebSocket, {
construct( target, args ) {
if ( serverIds && selectedServerId ) {
const url = new URL( args[ 0 ] );
const items = url.hostname.split( '.' );
if ( serverIds[ items[ 0 ] ] ) {
items[ 0 ] = selectedServerId;
url.hostname = items.join( '.' );
}
args[ 0 ] = url.toString();
}
const socket = Reflect.construct( ...arguments );
sockets.push( socket );
return socket;
}
} );
function reconnect() {
while ( sockets.length > 0 ) {
try {
sockets.shift().close();
} catch ( err ) {}
}
}
GM_addStyle( `
body {
margin: 0;
overflow: hidden;
background: #744100;
font-family: 'Ubuntu';
}
.group {
position: absolute;
right: 15px;
bottom: 15px;
display: flex;
flex-direction: column;
}
.group > * {
margin-bottom: 8px;
}
.group > *:last-child {
margin-bottom: 0;
}
.btn {
color: #fff;
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);
}
[tooltip] {
position: relative;
}
[tooltip]:after {
content: attr(tooltip);
font-size: 1rem;
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;
}
[tooltip]:not(.disabled):hover:after {
opacity: 1;
}
[tooltip]:is(.force-tooltip):after {
opacity: 1 !important;
}
.dialog {
position: absolute;
right: 85px;
bottom: 15px;
color: #fff;
background: #aeaeae;
padding: 0.75em;
border-radius: 0.3em;
box-shadow: inset 0 0 0 0.3em rgba(0, 0, 0, 0.25);
text-shadow: 1px 0 #000, -1px 0 #000, 0 1px #000, 0 -1px #000, 1px 1px #000, -1px -1px #000;
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);
}
}
` );
document.body.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">
<i class="fa fa-trophy"></i>
</div>
<div class="btn screenshot-btn" tooltip="Take screenshot">
<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 close-btn">close</div>
<div class="btn refresh-btn">refresh</div>
</div>
</div>
`;
const spinner = document.querySelector( '.spinner' );
fetchAllServers();
async function fetchAllServers() {
spinner.style.display = '';
await fetchServers( 'ffa' );
await fetchServers( 'teams' );
spinner.style.display = 'none';
}
const serverIds = {};
let selectedServerId = new URLSearchParams( document.location.search.substring( 1 ) ).get( 'server' );
if ( selectedServerId ) {
reconnect();
}
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;
dialog.appendChild( div );
div.onclick = function () {
const active = document.querySelector( '.option.active' );
if ( active ) {
active.classList.remove( 'active' );
}
this.classList.add( 'active' );
selectedServerId = id;
const url = new URL( window.location );
url.searchParams.set( 'server', selectedServerId );
history.pushState( {}, document.title, url );
reconnect();
}
if ( ! selectedServerId ) {
div.click();
} else if ( selectedServerId === id ) {
div.click();
}
}
}
}
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.getElementById( 'canvas' ).onclick = hideDialog;
document.querySelector( '.close-btn' ).onclick = hideDialog;
document.querySelector( '.refresh-btn' ).onclick = fetchAllServers;
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' );
},
function () {
copyCanvasToClipboard( document.querySelector( 'canvas' ) );
displayCopiedScreenshot();
}
);
leaderboardBtn.onclick = createClickListener(
function () {
if ( leaderboard ) {
downloadCanvas( getLeaderboardCanvas(), 'digdig_leaderboard' );
}
},
function () {
if ( leaderboard ) {
copyCanvasToClipboard( getLeaderboardCanvas() );
displayCopiedLeaderboard();
}
}
);
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;
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;
}
} )