save and view replays.
// ==UserScript==
// @name Gats.io - replay viewer
// @namespace http://tampermonkey.net/
// @version 1.2
// @description save and view replays.
// @author nitrogem35
// @match https://gats.io/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=gats.io
//
// ==/UserScript==
(function () {
'use strict';
//You are free to modify and redistribute my code, just make sure to give credit where it's due :)
var map = ['pistol','smg','shotgun','assault','sniper','lmg']
var socket
var token
var cookies = document.cookie.split("; ")
var obj = {}
//create a token for authentication if it doesn't exist (not accessing your login cookies btw)
for (var i in cookies) {
var cookie = cookies[i].split("=")
eval(`obj['${cookie[0]}'] = '${cookie[1]}'`)
}
if (obj.token) {
token = obj.token
}
else {
token = Math.random().toString(36).substring(4)
document.cookie = `token=${token}; expires=Tue, 19 Jan 2038 03:14:07 UTC;`
}
class Game {
constructor() {
this.spawnPacket = null
this.body = []
this.deathPacket = null
this.gameMode = null
}
}
var game = new Game()
var games = []
function getSocket() {
var loop = setInterval(() => {
try {
if (RF.list[0].socket.readyState == 1 && !RF.list[0].socket.url.includes("ping")) {
socket = RF.list[0].socket
socket.addEventListener('close', () => {
getElem("connected-status").innerHTML = "No 🔴"
socket = null
getSocket()
})
socket.addEventListener('message', function (event) {
var data = new TextDecoder().decode(event.data)
if (data == '.') return
var packets = data.split("|")
if (socket.url.includes("repl.co")) {
for (var i = 0; i < packets.length; i++) {
var vars = packets[i].split(",")
if(packets[i].startsWith("b")) {
if(vars[1] == c3) RD.pool[c3].playerAngle = parseInt(vars[6])
}
}
return
}
for (var i = 0; i < packets.length; i++) {
if (packets[i].startsWith("a")) {
game.spawnPacket = data
return
}
if (packets[i].startsWith("sta")) {
game.deathPacket = data
game.gameMode = c22
games.push(JSON.parse(JSON.stringify(game)))
game = new Game()
uploadReplays()
return
}
}
game.body.push(data)
})
getElem("connected-status").innerHTML = "Yes 🟢"
clearInterval(loop)
}
}
catch (err) {
//ignore errors
}
}, 100)
}
getSocket()
async function auth() {
var url = "https://nitrogem.loca.lt/authenticate"
var resp = await fetch(url, {
headers: {
"token": token,
"bypass-tunnel-reminder": true
}
})
var code = await resp.text()
getElem("auth-status").innerText = "Send a DM (g auth <code>) to the ShowStatsFr Discord bot with the following code:"
getElem("auth-code").innerHTML = `<code>${code}</code>`
}
unsafeWindow.auth = auth
async function checkAuth() {
var url = "https://nitrogem.loca.lt/checkAuth"
var resp = await fetch(url, {
headers: {
"token": token,
"bypass-tunnel-reminder": true
}
})
if(resp.status == 202) return undefined
else {
var username = await resp.text()
getElem("account-status").innerHTML = username
}
}
unsafeWindow.checkAuth = checkAuth
checkAuth()
async function loadReplays() {
var url = "https://nitrogem.loca.lt/replays"
var resp = await fetch(url, {
headers: {
"token": token,
"bypass-tunnel-reminder": true
}
})
if(resp.status == 202) return undefined
else {
var replays = await resp.json()
getElem("replayBody").innerHTML = ''
for(var i = 0; i < Object.keys(replays).length; i++) {
var gameMode = replays[Object.keys(replays)[i]].gameMode
var tr = document.createElement("tr")
var killCount = document.createElement("td")
killCount.innerHTML = replays[Object.keys(replays)[i]].killCount
tr.appendChild(killCount)
var utcDate = Object.keys(replays)[i]
var localDate = new Date(parseInt(utcDate))
var minutes = localDate.getMinutes()
if (minutes < 10) minutes = `0${minutes}`
var seconds = localDate.getSeconds()
if (seconds < 10) seconds = `0${seconds}`
var formattedDate = `${localDate.getMonth() + 1}/${localDate.getDate()}/${localDate.getFullYear()} @${localDate.getHours()}:${minutes}:${seconds}`
var date = document.createElement("td")
date.innerHTML = formattedDate
tr.appendChild(date)
var btn = document.createElement("span")
btn.innerHTML = `<button class="btn btn-info" onclick="a120('replay-loader.nitrogem35.repl.co/${utcDate}','${gameMode}', 'public')">View</button>`
tr.appendChild(btn)
getElem("replayBody").appendChild(tr)
}
}
}
unsafeWindow.loadReplays = loadReplays
//unsafeWindow allows this function to be called when you, say, click a button
function uploadReplays() {
if (games.length == 0) return
var opacity = 100
getElem("upload").innerHTML = `Uploading games (${games.length})`
getElem("upload").style.opacity = opacity / 100
var url = "https://nitrogem.loca.lt/stats"
postData(url, games[0]).then((data) => {
if (data == "ok") {
games.shift()
getElem("upload").innerHTML = `Game uploaded.`
setTimeout(() => {
var lowerOpacity = setInterval(() => {
if (opacity <= 0) clearInterval(lowerOpacity)
getElem("upload").style.opacity = opacity / 100
opacity--
}, 16)
}, 500)
}
if (data.startsWith("ERR")) {
games.shift()
getElem("upload").innerHTML = `${data}`
setTimeout(() => {
var lowerOpacity = setInterval(() => {
if (opacity <= 0) clearInterval(lowerOpacity)
getElem("upload").style.opacity = opacity / 100
opacity--
}, 16)
}, 500)
}
})
.catch((err) => {
games.shift()
getElem("upload").innerHTML = `Game failed to upload.`
setTimeout(() => {
var lowerOpacity = setInterval(() => {
if (opacity <= 0) clearInterval(lowerOpacity)
getElem("upload").style.opacity = opacity / 100
opacity--
}, 16)
}, 500)
})
}
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: "POST",
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
"token": token,
"bypass-tunnel-reminder": true
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(data)
})
return response.text()
}
var params = new URLSearchParams(window.location.search)
var replayId = params.get('replayId')
if(replayId) {
var loop = setInterval(() => {
if (!RF.list[0].socket.readyState == 1 || RF.list[0].socket.url.includes("ping")) return
fetch('https://nitrogem.loca.lt/gameType', {
"timestamp": replayId,
"bypass-tunnel-reminder": true
})
.then(data => data.text())
.then(gameType => {
RF.list[0].socket.close()
a120(`replay-loader.nitrogem35.repl.co/${replayId}`,`${gameType}`, 'public')
})
clearInterval(loop)
}, 100)
}
//create a gui on the right side of the page
var gui = document.createElement('div')
gui.id = 'gui'
gui.style.position = 'fixed'
gui.style.right = '0'
gui.style.top = '50%'
gui.style.zIndex = '9999'
document.body.appendChild(gui)
gui.innerHTML = `
<b>Gats.io - replay viewer</b>
<div>Connected: <span id="connected-status">No 🔴</span></div>
<div>Account: <span id="account-status">N/A</span></div>
<div id="upload"></div>
<div>End replay [1]</div>
<div>Toggle GUI [2]</div>
<style>
#gui {
user-select: none;
}
</style>
`
document.addEventListener('keydown', function(event) {
if (event.keyCode == 49) RF.list[0].socket.send(e('z'))
if (event.keyCode == 50) {
if(gui.style.display == 'none') gui.style.display = ''
else gui.style.display = 'none'
}
})
function e(msg) {
return new TextEncoder().encode(msg)
}
function getElem(id) {
return document.getElementById(id)
}
var append1 = new DOMParser().parseFromString(`
<style id="style1">
@media(max-width: 650px) {
#authButton {
padding: 3px 5px;
font-size: 14px
}
}
@media(min-width: 650px) and (max-width:849px) {
#authButton {
padding: 5px 8px
}
}
@media(min-width: 850px) and (max-width:1059px) {
#authButton {
padding: 5px 8px
}
}
</style>
<button id="authButton" type="button" class="auth-btn btn btn-lg" data-toggle="modal" data-target="#authModal"
style="display: inline-block;" draggable="true" unselectable="off" onclick="auth()">Authenticate</button>
`, 'text/html')
var style1 = append1.getElementById("style1")
var authBtn = append1.getElementById("authButton")
getElem("actrls").prepend(style1)
getElem("actrls").prepend(authBtn)
var append2 = new DOMParser().parseFromString(`
<div class="modal fade" tabindex="-1" role="dialog" id="authModal" style="display: none;">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title">Authentication</h4>
</div>
<div class="modal-body">
<p id="auth-status">Getting auth code...</p>
<b id="auth-code"></b>
<p id="spacer"></p>
<p id="notice">Note: Auth codes expire after 10 minutes.</p>
<a href="https://discord.gg/2jBb4Ga28P">Don't share any servers with the bot? Click here</a>
</div>
<div class="modal-footer">
<button id="refreshStatus" onclick="checkAuth()" class="btn btn-success">Refresh Auth Status</button>
</div>
</div>
</div>
</div>
`, 'text/html')
var authModal = append2.getElementById("authModal")
document.body.appendChild(authModal)
var append3 = new DOMParser().parseFromString(`
<li id="replayTab">
<a data-toggle="tab" href="#replayList" onclick="loadReplays()">
Replays
</a>
</li>
<div id="replayList" class="tab-pane fade">
<label style="margin-top:10px;">View one of your replays</label>
<table class="table table-striped server-table">
<thead>
<tr>
<th>Kills</th>
<th>Date</th>
<th></th>
</tr>
</thead>
<tbody id="replayBody" class="server-table-body">
</tbody>
</table>
</div>
`, 'text/html')
var replayTab = append3.getElementById("replayTab")
document.getElementsByClassName("nav nav-tabs")[0].appendChild(replayTab)
var replayList = append3.getElementById("replayList")
document.getElementsByClassName("tab-content")[0].appendChild(replayList)
setInterval(() => {
getElem("announcementMessage").textContent = `Note: Replay speed is 0.5x the position of the gun you picked. (${(map.indexOf(c1.weapon) * 0.5) + 0.5})`
}, 50)
})();