410 lines
18 KiB
JavaScript
410 lines
18 KiB
JavaScript
// ==UserScript==
|
|
// @name PhantomBot-ChatGuessr
|
|
// @namespace http://tampermonkey.net/
|
|
// @version 0.1
|
|
// @description try to take over the world!
|
|
// @author Sora
|
|
// @match https://*.geoguessr.com/*
|
|
// @icon https://www.google.com/s2/favicons?domain=geoguessr.com
|
|
// @require https://code.jquery.com/jquery-3.6.0.min.js
|
|
// @require https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/js/jquery.dataTables.min.js
|
|
// @grant none
|
|
// ==/UserScript==
|
|
if(localStorage.getItem('lauth')==null || localStorage.getItem('lauth')=="") localStorage.setItem('lauth',prompt("Enter Current Auth token",""))
|
|
|
|
var socket = null;
|
|
var isInGame = false;
|
|
var table;
|
|
var guesses = {};
|
|
window.MAP = null;
|
|
|
|
class GameHelper {
|
|
static isGameURL(url){ return url.includes("/game/"); }
|
|
static getGameId(url){ return url.substring(url.lastIndexOf("/") + 1); }
|
|
static sortByDistance(guesses){ return guesses.sort((a, b) => a.distance - b.distance); }
|
|
static sortByScore(guesses){ return guesses.sort((a, b) => b.score - a.score); }
|
|
}
|
|
|
|
const toMeter = (distance) => (distance >= 1 ? parseFloat(distance.toFixed(1)) + "km" : parseInt(distance * 1000) + "m");
|
|
const renderGuesses = (guesses) => {
|
|
let sguesses = GameHelper.sortByScore(guesses);
|
|
const rows = sguesses.map((guess, i) => {
|
|
return {
|
|
Position: i+1,
|
|
Player: `<span class='username' style='color:${guess.color}'>${guess.user}</span>`,
|
|
Streak: "-",
|
|
Distance: guess.distance,
|
|
Score: guess.score,
|
|
};
|
|
});
|
|
table.clear().draw();
|
|
table.rows.add(rows).draw();
|
|
};
|
|
|
|
|
|
let markers = [];
|
|
let polylines = [];
|
|
function populateMap(location, scores) {
|
|
|
|
scores = GameHelper.sortByScore(scores);
|
|
|
|
const infowindow = new google.maps.InfoWindow();
|
|
const icon = {
|
|
path: `M13.04,41.77c-0.11-1.29-0.35-3.2-0.99-5.42c-0.91-3.17-4.74-9.54-5.49-10.79c-3.64-6.1-5.46-9.21-5.45-12.07
|
|
c0.03-4.57,2.77-7.72,3.21-8.22c0.52-0.58,4.12-4.47,9.8-4.17c4.73,0.24,7.67,3.23,8.45,4.07c0.47,0.51,3.22,3.61,3.31,8.11
|
|
c0.06,3.01-1.89,6.26-5.78,12.77c-0.18,0.3-4.15,6.95-5.1,10.26c-0.64,2.24-0.89,4.17-1,5.48C13.68,41.78,13.36,41.78,13.04,41.77z
|
|
`,
|
|
fillColor: "#de3e3e",
|
|
fillOpacity: 0.7,
|
|
scale: 1.2,
|
|
strokeColor: "#000000",
|
|
strokeWeight: 1,
|
|
anchor: new google.maps.Point(14, 43),
|
|
labelOrigin: new google.maps.Point(13.5, 15),
|
|
};
|
|
|
|
const locationMarker = new google.maps.Marker({
|
|
position: location,
|
|
url: `http://maps.google.com/maps?q=&layer=c&cbll=${location.lat},${location.lng}`,
|
|
icon: icon,
|
|
map: MAP,
|
|
});
|
|
google.maps.event.addListener(locationMarker, "click", () => {
|
|
window.open(locationMarker.url, "_blank");
|
|
});
|
|
markers.push(locationMarker);
|
|
|
|
icon.scale = 1;
|
|
scores.forEach((score, index) => {
|
|
const color = index == 0 ? "#E3BB39" : index == 1 ? "#C9C9C9" : index == 2 ? "#A3682E" : score.color;
|
|
icon.fillColor = color;
|
|
let pos = {lat:parseFloat(score.location.split(',')[0]),lng:parseFloat(score.location.split(',')[1])}
|
|
const guessMarker = new google.maps.Marker({
|
|
position: pos,
|
|
icon: icon,
|
|
map: MAP,
|
|
label: { color: "#000", fontWeight: "bold", fontSize: "16px", text: `${score.user.substr(0,2)}` },
|
|
});
|
|
google.maps.event.addListener(guessMarker, "mouseover", () => {
|
|
infowindow.setContent(`
|
|
<p class="gm-iw__content">
|
|
<span style="font-size:14px;">${score.user}</span><br>
|
|
${score.distance >= 1 ? parseFloat(score.distance.toFixed(1)) + "km" : parseInt(score.distance * 1000) + "m"}<br>
|
|
${score.score}
|
|
</p>
|
|
`);
|
|
infowindow.open(MAP, guessMarker);
|
|
});
|
|
google.maps.event.addListener(guessMarker, "mouseout", () => {
|
|
infowindow.close();
|
|
});
|
|
markers.push(guessMarker);
|
|
|
|
polylines.push(
|
|
new google.maps.Polyline({
|
|
strokeColor: color,
|
|
strokeWeight: 4,
|
|
strokeOpacity: 0.6,
|
|
geodesic: true,
|
|
map: MAP,
|
|
path: [pos, location],
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
function clearMarkers() {
|
|
while (markers[0]) {
|
|
markers.pop().setMap(null);
|
|
}
|
|
while (polylines[0]) {
|
|
polylines.pop().setMap(null);
|
|
}
|
|
}
|
|
|
|
function hijackMap() {
|
|
const MAPS_API_URL = "https://maps.googleapis.com/maps/api/js?";
|
|
const GOOGLE_MAPS_PROMISE = new Promise((resolve, reject) => {
|
|
let scriptObserver = new MutationObserver((mutations) => {
|
|
for (const mutation of mutations) {
|
|
for (const node of mutation.addedNodes) {
|
|
if (node.tagName === "SCRIPT" && node.src.startsWith(MAPS_API_URL)) {
|
|
node.onload = () => {
|
|
scriptObserver.disconnect();
|
|
scriptObserver = undefined;
|
|
resolve();
|
|
};
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let bodyDone = false;
|
|
let headDone = false;
|
|
|
|
new MutationObserver((_, observer) => {
|
|
if (!bodyDone && document.body) {
|
|
if (scriptObserver) {
|
|
scriptObserver.observe(document.body, {
|
|
childList: true,
|
|
});
|
|
bodyDone = true;
|
|
}
|
|
}
|
|
if (!headDone && document.head) {
|
|
if (scriptObserver) {
|
|
scriptObserver.observe(document.head, {
|
|
childList: true,
|
|
});
|
|
headDone = true;
|
|
}
|
|
}
|
|
if (headDone && bodyDone) {
|
|
observer.disconnect();
|
|
}
|
|
}).observe(document.documentElement, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
});
|
|
|
|
function runAsClient(f) {
|
|
const script = document.createElement("script");
|
|
script.type = "text/javascript";
|
|
script.text = `(${f.toString()})()`;
|
|
document.body.appendChild(script);
|
|
}
|
|
|
|
GOOGLE_MAPS_PROMISE.then(() => {
|
|
runAsClient(() => {
|
|
const google = window.google;
|
|
const isGamePage = () => location.pathname.startsWith("/results/") || location.pathname.startsWith("/game/");
|
|
const onMapUpdate = (map) => {
|
|
try {
|
|
if (!isGamePage()) return;
|
|
MAP = map;
|
|
} catch (error) {
|
|
console.error("GeoguessrHijackMap Error:", error);
|
|
}
|
|
};
|
|
|
|
const oldMap = google.maps.Map;
|
|
google.maps.Map = Object.assign(
|
|
function (...args) {
|
|
const res = oldMap.apply(this, args);
|
|
this.addListener("idle", () => {
|
|
if (MAP != null) return;
|
|
onMapUpdate(this);
|
|
});
|
|
return res;
|
|
},
|
|
{
|
|
prototype: Object.create(oldMap.prototype),
|
|
}
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
const markerRemover = document.createElement("style");
|
|
markerRemover.innerHTML = ".map-pin{display:none}";
|
|
|
|
let gameState='none'
|
|
const handleGameState = async()=>{
|
|
let prevGameState = gameState;
|
|
let nextRoundBtn = document.querySelector('[data-qa="close-round-result"]');
|
|
let addGuessBtn = document.querySelector('[data-qa="perform-guess"]');
|
|
let playAgainBtn = document.querySelector('[data-qa="play-same-map"]');
|
|
if(gameState=='none'){
|
|
if(nextRoundBtn) gameState='result';
|
|
if(addGuessBtn) gameState='guess';
|
|
if(playAgainBtn) gameState='end';
|
|
}
|
|
if(gameState=='guess' && addGuessBtn){
|
|
markerRemover.remove();
|
|
addGuessBtn.addEventListener("click", async() => {
|
|
await new Promise(r => setTimeout(r, 500))
|
|
gameState='result';
|
|
socket.sendCommand('cg_refresh_gs_g', 'cga refresh', ()=>{});
|
|
handleGameState();
|
|
});
|
|
}else if(gameState=='result' && nextRoundBtn){
|
|
document.body.appendChild(markerRemover);
|
|
nextRoundBtn.addEventListener("click", async() => {
|
|
await new Promise(r => setTimeout(r, 500))
|
|
gameState='guess';
|
|
socket.sendCommand('cg_refresh_gs_r', 'cga refresh', ()=>{});
|
|
handleGameState();
|
|
});
|
|
}else if(gameState=='end' && playAgainBtn){
|
|
document.body.appendChild(markerRemover);
|
|
clearMarkers();
|
|
if(guesses){
|
|
renderGuesses(guesses.final);
|
|
for(let i = 0; i<= guesses.round; ++i){
|
|
populateMap(guesses.rounds[i],guesses[i])
|
|
}
|
|
}
|
|
playAgainBtn.addEventListener("click", async() => {
|
|
await new Promise(r => setTimeout(r, 500))
|
|
gameState='none';
|
|
socket.sendCommand('cg_refresh_gs_r', 'cga end', ()=>{ });
|
|
});
|
|
}else{
|
|
await new Promise(r => setTimeout(r, 500))
|
|
gameState='none'
|
|
handleGameState();
|
|
}
|
|
console.log(gameState)
|
|
}
|
|
|
|
|
|
const head = document.getElementsByTagName("head")[0];
|
|
|
|
const styles = document.createElement("style");
|
|
styles.innerHTML = `
|
|
.container_content__H3tXS > div:nth-child(1),.container_content__H3tXS > div:nth-child(2),.container_content__H3tXS > div:nth-child(3){display:none}
|
|
[data-qa=result-view-top]{max-height:100vh}
|
|
[data-qa=result-view-bottom]>div>div:last-child{display:none}
|
|
[data-qa=score]{display:none}
|
|
[data-qa=result-view-bottom]{position:fixed;width:100%;left:0;bottom:0}
|
|
[data-qa=result-view-bottom]>div{background:linear-gradient(transparent,#000),linear-gradient(90deg,#28374c36,#221d6ca1)}
|
|
.classic_section__19Ttr{display:none}button:focus{outline:0}.hud-button-group:last-child{display:none}.result-map__line{display:none}
|
|
.gm-ui-hover-effect{display:none!important}.gm-style-iw-c{top:-4px!important;padding:10px 0 0 17px!important}
|
|
.gm-style-iw-t::after{top:-4px!important}
|
|
.gm-iw__content{color:#000;text-align:center;font-weight:700; width:100%;}
|
|
#showScoreboard:hover{cursor:pointer;background:rgba(0,0,0,.7)}#settingsIcon span,#showScoreboard span{margin:-1px 0 0 1px}#scoreboardContainer{display:none;width:420px;position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;pointer-events:none}#scoreboard{font-family:Montserrat,sans-serif;position:relative;min-width:380px;min-height:180px;max-width:1800px;max-height:1000px;padding:5px;background-color:rgba(0,0,0,.4);box-shadow:2px 2px 7px -2px #000;color:#fff;text-align:center;border-radius:10px;pointer-events:auto;user-select:none;overflow:hidden;z-index:999999}.dataTables_scrollHeadInner{width:100%!important}#scoreboardHeader{display:grid;grid-template-columns:90px auto 80px;justify-items:center;height:35px;font-size:18px;align-items:center}#scoreboardTitle{margin-top:-8px}.dataTables_scrollBody::-webkit-scrollbar{display:none}.dataTables_scrollBody{padding-top:2px;-ms-overflow-style:none;scrollbar-width:none}table{font-size:15px;line-height:.8;font-weight:700;width:100%!important;margin:0 auto;clear:both;border-collapse:collapse;table-layout:fixed;word-wrap:break-word;overflow:hidden}thead{font-size:14px;background-color:rgba(0,0,0,.5)}tbody td,thead th{padding:8px 0;line-height:1em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}tbody .odd{background-color:rgba(0,0,0,.1)}tbody .even{background-color:rgba(0,0,0,.2)}tbody>tr:hover{-webkit-transition:.2s;transition:.2s;background-color:rgba(0,0,0,.4)}tbody tr:hover>.sorting_1,tbody tr>.sorting_1{background-color:rgba(0,0,0,.1)}th.sorting,th.sorting_asc,th.sorting_desc{cursor:pointer}th.sorting:hover,th.sorting_asc:hover,th.sorting_desc:hover{-webkit-transition:.1s;transition:.2s;color:#d6d6d6}.dataTables_empty{display:none}.icon{font-size:20px;line-height:0}.username{text-shadow:.05em 0 .05em #fff}.expand{animation:expand .3s ease-in-out}@keyframes expand{from{transform:scale(0);opacity:0}}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;touch-action:none}.ui-resizable-autohide .ui-resizable-handle,.ui-resizable-disabled .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:12px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:12px;width:100%;bottom:0;left:0}.ui-resizable-e{cursor:e-resize;width:12px;height:100%;right:-3px;top:0}.ui-resizable-w{cursor:w-resize;width:12px;height:100%;left:0;top:0}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:0;bottom:0}.ui-resizable-sw{cursor:sw-resize;width:12px;height:12px;left:0;bottom:0}.ui-resizable-nw{cursor:nw-resize;width:12px;height:12px;left:0;top:0}.ui-resizable-ne{cursor:ne-resize;width:12px;height:12px;right:-3px;top:-3px}.dt-buttons{position:absolute;top:0;margin-top:16px;width:100%;text-align:left}.dt-button{vertical-align:middle;font-size:14px;cursor:pointer;border-radius:5px;border:1px solid #000;background-size:200% auto;background-image:linear-gradient(to right,#2e2e2e 0,#454545 51%,#2e2e2e 100%);transition:.3s;-webkit-transition:.3s}.dt-button:hover{background-position:right center;box-shadow:2px 2px 5px -2px #000}.dt-button-collection{margin-top:-29px;position:absolute;padding-left:45px}.dt-button-collection div{background-color:#333;border-radius:8px;padding:4px 6px 4px 6px}.buttons-columnVisibility{color:#fff;padding:3px 7px}.buttons-columnVisibility.active{background-image:linear-gradient(to right,#1cd997 0,#33b09b 51%,#1cd997 100%)}.colvis-btn{width:40px;height:25px}.scrollBtn{text-align:center;width:38px;height:23px;margin-right:4px;float:left}.scrollBtn label span{margin-top:2px;height:100%;display:block;cursor:pointer}.scrollBtn label input{position:absolute;top:-50px}.scrollBtn input:checked+span{color:#1cd997}#switchContainer{margin-top:-9px;margin-left:auto;z-index:99;position:relative;display:inline-block;width:37px;height:21px;-webkit-transition:.1s;transition:.1s}#switchContainer:hover{box-shadow:2px 2px 7px -2px #000}#switchContainer input{display:none}.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;border-radius:4px;background-color:#e04352;-webkit-transition:.1s;transition:.1s}.slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:3px;border-radius:3px;background-color:#fff;-webkit-transition:.2s;transition:.2s}input:checked+.slider{background-color:#1cd997;box-shadow:2px 2px 7px -2px #000}input:checked+.slider:before{-webkit-transform:translateX(17px)}#scrollSpeedSlider{display:none;height:3px;padding:0;-webkit-appearance:none;width:calc(100% - 12px);background:#fff;outline:0;opacity:.2;-webkit-transition:.3s;transition:opacity .3s;direction:rtl}#scrollSpeedSlider:hover{opacity:1}#scrollSpeedSlider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:30px;height:7px;background:#63db85;cursor:pointer}#scrollSpeedSlider::-moz-range-thumb{width:30px;height:7px;background:#63db85;cursor:pointer}.btn{margin:30px;padding:5px;background-color:#22da8d;color:#fff;font-weight:700}.btn:hover{background-color:#18cc81}.btn:active{background-color:#15b472}`
|
|
head.appendChild(styles);
|
|
|
|
hijackMap();
|
|
const scoreboardContainer = document.createElement("div");
|
|
scoreboardContainer.setAttribute("id", "scoreboardContainer");
|
|
scoreboardContainer.innerHTML = `
|
|
<div id='scoreboard'>
|
|
<div id='scoreboardHeader'>
|
|
<span></span>
|
|
<span id='scoreboardTitle'>Guesses</span>
|
|
<label id='switchContainer'>
|
|
<input id='switchBtn' type='checkbox' />
|
|
<div class='slider'></div>
|
|
</label>
|
|
</div>
|
|
<table id='datatable' width='100%'>
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Player</th>
|
|
<th>Streak</th>
|
|
<th>Distance</th>
|
|
<th>Score</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id='guessList'></tbody>
|
|
</table>
|
|
</div>`;
|
|
document.body.appendChild(scoreboardContainer);
|
|
|
|
|
|
|
|
|
|
const script1 = document.createElement('script')
|
|
script1.src = 'https://twitchbot.helcel.net/common/reconnecting-websocket/reconnectingWS.min.js';
|
|
const script2= document.createElement('script')
|
|
script2.src = 'https://twitchbot.helcel.net/common/js/wsConfig.js';
|
|
const script3 = document.createElement('script')
|
|
script3.src ='https://twitchbot.helcel.net/custom/js/socketWrapper.js'
|
|
document.body.appendChild(script1)
|
|
document.body.appendChild(script2)
|
|
document.body.appendChild(script3)
|
|
script3.addEventListener('load', async () => {
|
|
while(!window.socket){await new Promise(r => setTimeout(r, 500))}
|
|
socket = window.socket;
|
|
if(socket){
|
|
socket.addFamilyHandler("chatguessr", (e)=>{
|
|
try {
|
|
let rawMessage = e.data, message = JSON.parse(rawMessage);
|
|
if(!message.hasOwnProperty('eventFamily') || message.eventFamily != 'chatguessr' ||
|
|
!message.hasOwnProperty('eventType') || !message.hasOwnProperty('data')) return;
|
|
if(message.eventType == 'guesses') {
|
|
const gd = JSON.parse(message.data)
|
|
guesses = gd;
|
|
if(gd.round!=undefined && gd[gd.round] && gd.rounds){
|
|
clearMarkers();
|
|
renderGuesses(gd[gd.round]);
|
|
if(gameState=="result"){
|
|
populateMap(gd.rounds[gd.round],gd[gd.round])
|
|
}
|
|
}else if(gd.round!= undefined && gd[gd.round - 1]){
|
|
clearMarkers();
|
|
renderGuesses([]);
|
|
}
|
|
}
|
|
} catch (ex) {console.log(ex)}
|
|
});
|
|
while(socket.getReadyState() === 0){await new Promise(r => setTimeout(r, 500))}
|
|
socket.sendCommand('cga_gg', 'cga gg', ()=>{});
|
|
|
|
var ccc = '';
|
|
setInterval(async()=>{
|
|
if(ccc != $('#__next > div').attr('class')){
|
|
|
|
if(GameHelper.isGameURL(window.location.href)){
|
|
$('#scoreboardContainer').show();
|
|
if(isInGame){
|
|
socket.sendCommand('cg_refresh', 'cga refresh', ()=>{});
|
|
}else {
|
|
isInGame = true;
|
|
socket.sendCommand('cga_start', 'cga start '+GameHelper.getGameId(window.location.href), ()=>{});
|
|
handleGameState();
|
|
}
|
|
isInGame = true;
|
|
}else if(isInGame){
|
|
isInGame = false;
|
|
socket.sendCommand('cga_end', 'cga end', ()=>{});
|
|
$('#scoreboardContainer').hide();
|
|
}else{
|
|
$('#scoreboardContainer').hide();
|
|
}
|
|
}
|
|
ccc = $('#__next > div').attr('class')
|
|
}, 1000);
|
|
}
|
|
socket.getDBValue('cga_init_btn', 'cgstatus', 'isOpen', (v)=>$('#switchBtn').prop("checked","true"==v.cgstatus))
|
|
$('#switchBtn').change(()=>{
|
|
if($('#switchBtn').prop('checked')) socket.sendCommand('cga_open_btn', 'cga open', ()=>{});
|
|
else socket.sendCommand('cga_close_btn', 'cga close', ()=>{});
|
|
});
|
|
|
|
|
|
table = $("#datatable").DataTable({
|
|
info: false,
|
|
searching: false,
|
|
autoWidth: true,
|
|
paging: false,
|
|
scrollY: 166,
|
|
scrollResize: true,
|
|
scrollCollapse: true,
|
|
language: { zeroRecords: " " },
|
|
dom: "Bfrtip",
|
|
buttons: [],
|
|
columns: [
|
|
{ data: "Position" },
|
|
{ data: "Player" },
|
|
{ data: "Streak" },
|
|
{
|
|
data: "Distance",
|
|
render: (data, type) => ((type === "display" || type === "filter")? toMeter(data) : data)
|
|
},
|
|
{ data: "Score" },
|
|
],
|
|
columnDefs: [
|
|
{ targets: 0, width: "35px"},
|
|
{ targets: 1, width: "auto"},
|
|
{ targets: 2, width: "55px" },
|
|
{ targets: 3, width: "100px" },
|
|
{ targets: 4, width: "75px", type: "natural" },
|
|
],
|
|
});
|
|
});
|