DigDig.IO Server Selector

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

当前为 2021-07-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DigDig.IO Server Selector
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.0
  5. // @description Server selector for digdig.io. Double click to copy, single click to download.
  6. // @author Zertalious (Zert)
  7. // @match *://digdig.io/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant unsafeWindow
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. const sockets = [];
  14.  
  15. let initialServerId;
  16.  
  17. unsafeWindow.console.log = new Proxy( unsafeWindow.console.log, {
  18. apply( target, thisArgs, args ) {
  19.  
  20. const x = args[ 0 ];
  21.  
  22. if ( x.startsWith( 'Connecting to ' ) ) {
  23.  
  24. initialServerId = x.slice( 0, x.indexOf( '.' ) ).split( ' ' )[ 2 ];
  25.  
  26. }
  27.  
  28. return Reflect.apply( ...arguments );
  29.  
  30. }
  31. } )
  32.  
  33. unsafeWindow.WebSocket = new Proxy( unsafeWindow.WebSocket, {
  34. construct( target, args ) {
  35.  
  36. if ( selectedServerId ) {
  37.  
  38. const url = new URL( args[ 0 ] );
  39.  
  40. const items = url.hostname.split( '.' );
  41.  
  42. if ( serverIds[ items[ 0 ] ] || initialServerId === items[ 0 ] ) {
  43.  
  44. items[ 0 ] = selectedServerId;
  45.  
  46. url.hostname = items.join( '.' );
  47.  
  48. console.log( '[SERVER SELECTOR] Overriding...', { from: args[ 0 ], to: url.toString(), selectedServerId } );
  49.  
  50. }
  51.  
  52. args[ 0 ] = url.toString();
  53.  
  54. }
  55.  
  56. const socket = Reflect.construct( ...arguments );
  57.  
  58. sockets.push( socket );
  59.  
  60. return socket;
  61.  
  62. }
  63. } );
  64.  
  65. function reconnect() {
  66.  
  67. while ( sockets.length > 0 ) {
  68.  
  69. try {
  70.  
  71. sockets.shift().close();
  72.  
  73. } catch ( err ) {}
  74.  
  75. }
  76.  
  77. }
  78.  
  79. GM_addStyle( `
  80.  
  81. body {
  82. margin: 0;
  83. overflow: hidden;
  84. background: #744100;
  85. font-family: 'Ubuntu';
  86. }
  87.  
  88. .group {
  89. position: absolute;
  90. right: 15px;
  91. bottom: 15px;
  92. display: flex;
  93. flex-direction: column;
  94. }
  95.  
  96. .group > * {
  97. margin-bottom: 8px;
  98. }
  99.  
  100. .group > *:last-child {
  101. margin-bottom: 0;
  102. }
  103.  
  104. .btn {
  105. color: #fff;
  106. background: #aeaeae;
  107. font-size: 2.25em;
  108. text-align: center;
  109. padding: 0.2em;
  110. cursor: pointer;
  111. box-shadow: inset 0 0 0 0.1em rgba(0, 0, 0, 0.25);
  112. border-radius: 0.1em;
  113. position: relative;
  114. user-select: none;
  115. }
  116.  
  117. .btn:before {
  118. content: ' ';
  119. position: absolute;
  120. top: 0.1em;
  121. left: 0.1em;
  122. width: calc(100% - 0.2em);
  123. height: calc(100% - 0.2em);
  124. background: transparent;
  125. }
  126.  
  127. .btn:hover:before {
  128. background: hsla(0, 0%, 100%, 0.25);
  129. }
  130.  
  131. .btn:active:before {
  132. background: rgba(0, 0, 0, 0.1);
  133. }
  134.  
  135. [tooltip] {
  136. position: relative;
  137. }
  138.  
  139. [tooltip]:after {
  140. content: attr(tooltip);
  141. font-size: 1rem;
  142. position: absolute;
  143. right: 100%;
  144. top: 50%;
  145. transform: translate(-10px, -50%);
  146. white-space: nowrap;
  147. pointer-events: none;
  148. background: rgba(0, 0, 0, 0.5);
  149. padding: 0.25em 0.4em;
  150. border-radius: 0.2em;
  151. opacity: 0;
  152. transition: 0.2s;
  153. }
  154.  
  155. [tooltip]:not(.disabled):hover:after {
  156. opacity: 1;
  157. }
  158.  
  159. [tooltip]:is(.force-tooltip):after {
  160. opacity: 1 !important;
  161. }
  162.  
  163. .dialog {
  164. position: absolute;
  165. right: 85px;
  166. bottom: 15px;
  167. color: #fff;
  168. background: #aeaeae;
  169. padding: 0.75em;
  170. border-radius: 0.3em;
  171. box-shadow: inset 0 0 0 0.3em rgba(0, 0, 0, 0.25);
  172. text-shadow: 1px 0 #000, -1px 0 #000, 0 1px #000, 0 -1px #000, 1px 1px #000, -1px -1px #000;
  173. width: 300px;
  174. transition: 0.2s;
  175. }
  176.  
  177. .dialog > * {
  178. margin-bottom: 5px;
  179. }
  180.  
  181. .dialog > *:last-child {
  182. margin-bottom: 0;
  183. }
  184.  
  185. .dialog.disabled {
  186. transform: translate(0, calc(100% + 15px));
  187. }
  188.  
  189. .dialog .btn {
  190. font-size: 1.25rem;
  191. background: #bb5555;
  192. }
  193.  
  194. .title {
  195. font-size: 1.5em;
  196. text-align: center;
  197. }
  198.  
  199. .spinner {
  200. margin: 10px auto;
  201. width: 60px;
  202. height: 60px;
  203. border: 10px solid transparent;
  204. border-top-color: rgba(0, 0, 0, 0.3);
  205. border-radius: 50%;
  206. animation: spin 0.5s infinite;
  207. }
  208.  
  209. .option {
  210. background: rgba(0, 0, 0, 0.1);
  211. padding: 0.5em 0.75em;
  212. border-radius: 0.25em;
  213. cursor: pointer;
  214. }
  215.  
  216. .option.active {
  217. box-shadow: inset 0 0 0 0.15em rgba(0, 0, 0, 0.2);
  218. }
  219.  
  220. @keyframes spin {
  221. from {
  222. transform: rotate(0);
  223. }
  224.  
  225. to {
  226. transform: rotate(360deg);
  227. }
  228. }
  229.  
  230. ` );
  231.  
  232. document.body.innerHTML += `
  233.  
  234. <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  235. <div class="group">
  236. <div class="btn leaderboard-btn" tooltip="Capture leaderboard">
  237. <i class="fa fa-trophy"></i>
  238. </div>
  239. <div class="btn screenshot-btn" tooltip="Take screenshot">
  240. <i class="fa fa-camera"></i>
  241. </div>
  242. <div class="btn servers-btn" tooltip="Servers">
  243. <i class="fa fa-globe"></i>
  244. </div>
  245. </div>
  246. <div class="dialog disabled">
  247. <div class="title">Servers</div>
  248. <div class="spinner"></div>
  249. <div class="btn close-btn">close</div>
  250. <div class="btn refresh-btn">refresh</div>
  251. </div>
  252. </div>
  253.  
  254. `;
  255.  
  256. const spinner = document.querySelector( '.spinner' );
  257.  
  258. ( async function () {
  259.  
  260. await fetchServers( 'ffa' );
  261. await fetchServers( 'teams' );
  262.  
  263. spinner.style.display = 'none';
  264.  
  265. } )();
  266.  
  267. const serverIds = {};
  268.  
  269. let selectedServerId = new URLSearchParams( document.location.search.substring( 1 ) ).get( 'server' );
  270.  
  271. if ( selectedServerId ) {
  272.  
  273. reconnect();
  274.  
  275. }
  276.  
  277. async function fetchServers( mode ) {
  278.  
  279. const response = await fetch( 'https://api.n.m28.io/endpoint/digdig-' + mode + '/findEach' );
  280. const json = await response.json();
  281.  
  282. for ( let key in json.servers ) {
  283.  
  284. const id = json.servers[ key ].id;
  285.  
  286. if ( ! serverIds[ id ] ) {
  287.  
  288. serverIds[ id ] = true;
  289.  
  290. const div = document.createElement( 'div' );
  291. div.classList.add( 'option' );
  292. div.innerHTML = mode + '_' + key.split( '-' )[ 1 ] + '_' + id;
  293.  
  294. dialog.appendChild( div );
  295.  
  296. div.onclick = function () {
  297.  
  298. const active = document.querySelector( '.option.active' );
  299.  
  300. if ( active ) {
  301.  
  302. active.classList.remove( 'active' );
  303.  
  304. }
  305.  
  306. this.classList.add( 'active' );
  307.  
  308. selectedServerId = id;
  309.  
  310. const url = new URL( window.location );
  311. url.searchParams.set( 'server', selectedServerId );
  312.  
  313. history.pushState( {}, document.title, url );
  314.  
  315. reconnect();
  316.  
  317. }
  318.  
  319. if ( ! selectedServerId ) {
  320.  
  321. div.click();
  322.  
  323. } else if ( selectedServerId === id ) {
  324.  
  325. div.click();
  326.  
  327. }
  328.  
  329. }
  330.  
  331. }
  332.  
  333. }
  334.  
  335. const dialog = document.querySelector( '.dialog' );
  336. const serversBtn = document.querySelector( '.servers-btn' );
  337.  
  338. serversBtn.onclick = function () {
  339.  
  340. if ( dialog.classList.contains( 'disabled' ) ) {
  341.  
  342. dialog.classList.remove( 'disabled' );
  343. this.classList.add( 'disabled' );
  344.  
  345. } else {
  346.  
  347. hideDialog();
  348.  
  349. }
  350.  
  351. }
  352.  
  353. document.getElementById( 'canvas' ).onclick = hideDialog;
  354. document.querySelector( '.close-btn' ).onclick = hideDialog;
  355.  
  356. document.querySelector( '.refresh-btn' ).onclick = async function () {
  357.  
  358. spinner.style.display = '';
  359.  
  360. await fetchServers( 'ffa' );
  361. await fetchServers( 'teams' );
  362.  
  363. spinner.style.display = 'none';
  364.  
  365. };
  366.  
  367. function hideDialog() {
  368.  
  369. dialog.classList.add( 'disabled' );
  370. serversBtn.classList.remove( 'disabled' );
  371.  
  372. }
  373.  
  374. function createClickListener( a, b ) {
  375.  
  376. let clicks = 0;
  377.  
  378. return function () {
  379.  
  380. clicks ++;
  381.  
  382. setTimeout( function () {
  383.  
  384. if ( clicks === 1 ) {
  385.  
  386. a();
  387.  
  388. }
  389.  
  390. clicks = 0;
  391.  
  392. }, 300 );
  393.  
  394. if ( clicks === 2 ) {
  395.  
  396. b();
  397.  
  398. }
  399.  
  400. }
  401.  
  402. }
  403.  
  404. const screenshotBtn = document.querySelector( '.screenshot-btn' );
  405. const leaderboardBtn = document.querySelector( '.leaderboard-btn' );
  406.  
  407. const displayCopiedScreenshot = createCopiedDisplayer( screenshotBtn );
  408. const displayCopiedLeaderboard = createCopiedDisplayer( leaderboardBtn );
  409.  
  410. screenshotBtn.onclick = createClickListener(
  411. function () {
  412.  
  413. downloadCanvas( document.querySelector( 'canvas' ), 'digdig' );
  414.  
  415. },
  416. copyScreenshot
  417. );
  418.  
  419. leaderboardBtn.onclick = createClickListener(
  420. function () {
  421.  
  422. if ( leaderboard ) {
  423.  
  424. downloadCanvas( getLeaderboardCanvas(), 'digdig_leaderboard' );
  425.  
  426. }
  427.  
  428. },
  429. copyLeaderboard
  430. );
  431.  
  432. function copyScreenshot() {
  433.  
  434. copyCanvasToClipboard( document.querySelector( 'canvas' ) );
  435.  
  436. displayCopiedScreenshot();
  437.  
  438. }
  439.  
  440. function copyLeaderboard() {
  441.  
  442. if ( leaderboard ) {
  443.  
  444. copyCanvasToClipboard( getLeaderboardCanvas() );
  445.  
  446. displayCopiedLeaderboard();
  447.  
  448. }
  449.  
  450. }
  451.  
  452. window.addEventListener( 'keyup', function ( event ) {
  453.  
  454. const key = String.fromCharCode( event.keyCode );
  455.  
  456. if ( key === 'F' ) {
  457.  
  458. copyScreenshot();
  459.  
  460. } else if ( key === 'G' ) {
  461.  
  462. copyLeaderboard();
  463.  
  464. }
  465.  
  466. } );
  467.  
  468. function createCopiedDisplayer( element ) {
  469.  
  470. let old, timeout;
  471.  
  472. return function () {
  473.  
  474. if ( element.classList.contains( 'force-tooltip' ) ) {
  475.  
  476. clearTimeout( timeout );
  477.  
  478. } else {
  479.  
  480. old = element.getAttribute( 'tooltip' );
  481. element.setAttribute( 'tooltip', 'Copied!' );
  482. element.classList.add( 'force-tooltip' );
  483.  
  484. }
  485.  
  486. timeout = setTimeout( function () {
  487.  
  488. element.setAttribute( 'tooltip', old );
  489. element.classList.remove( 'force-tooltip' );
  490.  
  491. }, 1000 );
  492.  
  493. }
  494.  
  495. }
  496.  
  497. function getLeaderboardCanvas() {
  498.  
  499. const offset = - 115;
  500.  
  501. const canvas = document.createElement( 'canvas' );
  502. canvas.width = leaderboard.width;
  503. canvas.height = leaderboard.height + offset;
  504.  
  505. canvas.getContext( '2d' ).drawImage( leaderboard, 0, offset );
  506.  
  507. return canvas;
  508.  
  509. }
  510.  
  511. function copyCanvasToClipboard( canvas ) {
  512.  
  513. canvas.toBlob( function ( blob ) {
  514.  
  515. navigator.clipboard.write( [ new ClipboardItem( { 'image/png': blob } ) ] );
  516.  
  517. } );
  518.  
  519. }
  520.  
  521. function downloadCanvas( canvas, filename ) {
  522.  
  523. const a = document.createElement( 'a' );
  524.  
  525. a.href = canvas.toDataURL();
  526. a.download = filename + '_' + Date.now() + '.png';
  527.  
  528. a.click();
  529.  
  530. }
  531.  
  532. let leaderboard;
  533. let leaderboardPartial;
  534.  
  535. const Canvas = unsafeWindow.OffscreenCanvas ? unsafeWindow.OffscreenCanvas.prototype : unsafeWindow.HTMLCanvasElement.prototype;
  536.  
  537. Canvas.getContext = new Proxy( Canvas.getContext, {
  538. apply() {
  539.  
  540. const ctx = Reflect.apply( ...arguments );
  541.  
  542. ctx.fillText = new Proxy( ctx.fillText, {
  543. apply( target, thisArgs, args ) {
  544.  
  545. if ( args[ 0 ].indexOf( 'Diggers' ) > - 1 ) {
  546.  
  547. leaderboardPartial = ctx.canvas;
  548.  
  549. }
  550.  
  551. return Reflect.apply( ...arguments );
  552.  
  553. }
  554. } );
  555.  
  556. ctx.drawImage = new Proxy( ctx.drawImage, {
  557. apply( target, thisArgs, args ) {
  558.  
  559. if ( args[ 0 ] === leaderboardPartial ) {
  560.  
  561. leaderboard = ctx.canvas;
  562.  
  563. }
  564.  
  565. return Reflect.apply( ...arguments );
  566.  
  567. }
  568. } );
  569.  
  570. return ctx;
  571.  
  572. }
  573. } )

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址