bonktools 3.2.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -247
- package/dependencies/CondensedInjector.js +18 -0
- package/dependencies/sgrAPI.user.js +526 -0
- package/dist/bot.js +15 -0
- package/dist/browser/browserManager.js +156 -0
- package/dist/browser/roomMaker.js +459 -0
- package/dist/config/room.js +23 -0
- package/dist/index.js +96 -0
- package/dist/lib/index.js +15 -0
- package/dist/messages.js +87 -0
- package/dist/types/constants.types.js +59 -0
- package/dist/types/joinTeam.types.js +10 -0
- package/dist/utils/botExtensions.js +75 -0
- package/dist/utils/investigationTriggerStart.js +21 -0
- package/dist/utils/logger.js +48 -0
- package/package.json +39 -33
- package/bonk_fullchain.pem +0 -37
- package/examples/host-bot.js +0 -63
- package/examples/simple-bot.js +0 -127
- package/old/bonkbot.js +0 -1266
- package/old/examplebot.js +0 -64
- package/scripts/download-cert.js +0 -52
- package/src/bot.js +0 -1470
- package/src/index.js +0 -23
- package/src/packet.js +0 -374
- package/src/utils/constants.js +0 -153
- package/src/utils/errors.js +0 -137
- package/src/utils/logger.js +0 -109
- package/src/utils/validation.js +0 -130
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
// ==UserScript==
|
|
2
|
+
// @name bonk.io sgrAPI
|
|
3
|
+
// @namespace Violentmonkey Scripts
|
|
4
|
+
// @match https://bonk.io/gameframe-release.html
|
|
5
|
+
// @run-at document-start
|
|
6
|
+
// @grant none
|
|
7
|
+
// @version 1.0
|
|
8
|
+
// @author StarCubey
|
|
9
|
+
// @license MIT
|
|
10
|
+
// @description An API for bots or doing various actions programatically.
|
|
11
|
+
// ==/UserScript==
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
This mod requires Excigma's code injector to be installed
|
|
15
|
+
https://greasyfork.org/en/scripts/433861-code-injector-bonk-io
|
|
16
|
+
|
|
17
|
+
This mod is not compatible with Salama's bonk host because you can't inject Salama's football step regex twice.
|
|
18
|
+
|
|
19
|
+
All regexes and most reverse engineering logic is copied from "bonk-host" and "bonk-playlists" by Salama.
|
|
20
|
+
Otherwise, the code is written by me and licensed under MIT.
|
|
21
|
+
https://github.com/Salama/bonk-host
|
|
22
|
+
https://github.com/Salama/bonk-playlists
|
|
23
|
+
|
|
24
|
+
Source / reference: https://github.com/StarCubey/bonk-rating-bot (sgrAPI from StarCubey).
|
|
25
|
+
Note from author: prepare for breaking changes; migration to a custom injector is in progress.
|
|
26
|
+
|
|
27
|
+
Copyright (c) 2021 Salama
|
|
28
|
+
|
|
29
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
30
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
31
|
+
in the Software without restriction, including without limitation the rights
|
|
32
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
33
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
34
|
+
furnished to do so, subject to the following conditions:
|
|
35
|
+
|
|
36
|
+
The above copyright notice and this permission notice shall be included in all
|
|
37
|
+
copies or substantial portions of the Software.
|
|
38
|
+
|
|
39
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
40
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
41
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
42
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
43
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
44
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
45
|
+
SOFTWARE.
|
|
46
|
+
|
|
47
|
+
You can access the window.sgrAPI object from inspect element. Just make sure you're in the correct frame which can be done by
|
|
48
|
+
right clicking the game and selecting inspect element or by selecting the frame gameframe-release.html.
|
|
49
|
+
|
|
50
|
+
window.sgrAPI.players
|
|
51
|
+
This is an array of players where the index is the player id and the value has player specific information.
|
|
52
|
+
The player array may have a lot of empty spaces (with a value of null) since each new player is given a new id.
|
|
53
|
+
|
|
54
|
+
window.sgrAPI.toolFunctions.networkEngine
|
|
55
|
+
Contains functions for common actions like moving players, changing host, or kicking players.
|
|
56
|
+
Many of these functions take a player id as a parameter.
|
|
57
|
+
|
|
58
|
+
networkEngine functions and variables:
|
|
59
|
+
getLSID() - Gets your player id
|
|
60
|
+
hostID - Variable that contains the id of the host
|
|
61
|
+
chatMessage("message") - Sends a chat message
|
|
62
|
+
kickPlayer(id)
|
|
63
|
+
banPlayer(id)
|
|
64
|
+
changeOwnTeam(team) - Spectate = 0, Playing = 1, Red = 2, Blue = 3, Green = 4, Yellow = 5
|
|
65
|
+
changeOtherTeam(id, team)
|
|
66
|
+
doTeamLock(true/false) - locks or unlocks teams
|
|
67
|
+
sendNoHostSwap(true/false) - If set to false, host does not swap on disconnect.
|
|
68
|
+
setReady(true/false) - Changes your ready state.
|
|
69
|
+
allReadyReset() - Sets the ready state of all players.
|
|
70
|
+
sendStartCountdown(num) - Sends "Game starting in num"
|
|
71
|
+
|
|
72
|
+
window.sgrAPI.state and sgrBotAPI.footballState
|
|
73
|
+
The state values have useful information like score and player position.
|
|
74
|
+
|
|
75
|
+
window.sgrAPI.state.scores and window.sgrAPI.footballState.scores
|
|
76
|
+
For team games, the score array will have 4 numbers corresponding to different team colors.
|
|
77
|
+
For non-team games, the score array will be similar to the player array where scores[playerId] gives you the score of that player.
|
|
78
|
+
|
|
79
|
+
window.sgrAPI.nextScores
|
|
80
|
+
Setting this variable to a score array will load that score at the beginning of the next game.
|
|
81
|
+
This is similar to the Bonk Host keep scores feature. window.sgrBotAPI.nextScores will be undefined when not in use.
|
|
82
|
+
|
|
83
|
+
sgrAPI.stateFunctions.hostHandlePlayerJoined(id, sgrAPI.players.length, team)
|
|
84
|
+
This is bonk host freejoin. This moves a player into a game so that they appear in the next round.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
window.sgrAPI = {};
|
|
88
|
+
// Evitar erro no WebSocket quando onReceive ainda não foi definido (ex.: antes de configureRoom)
|
|
89
|
+
window.sgrAPI.onReceive = () => true;
|
|
90
|
+
|
|
91
|
+
//You can wait for the script to be injected with: await sgrAPI.injected;
|
|
92
|
+
window.sgrAPI.injectedResolve;
|
|
93
|
+
window.sgrAPI.injected = new Promise(res => sgrAPI.injectedResolve = () => res());
|
|
94
|
+
|
|
95
|
+
window.sgrAPI.startGame = () => {
|
|
96
|
+
for(let callback of Object.keys(window.sgrAPI.bonkCallbacks)) {
|
|
97
|
+
window.sgrAPI.bonkCallbacks[callback]("startGame");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Returns a condensed version of window.sgrBotAPI.players with no null values where each player has an id property.
|
|
102
|
+
window.sgrAPI.getPlayers = () => {
|
|
103
|
+
if (window.sgrAPI.players == null) return [];
|
|
104
|
+
return Object.keys(window.sgrAPI.players)
|
|
105
|
+
.map(i => {
|
|
106
|
+
let player = window.sgrAPI.players[i];
|
|
107
|
+
if(player === null) return undefined;
|
|
108
|
+
player.id = Number(i);
|
|
109
|
+
return player;
|
|
110
|
+
}).filter(p => p);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Football = "f", Simple = "bs", Death Arrows = "ard", Arrows = "ar", Grapple = "sp", VTOL = "v", and Classic = "b".
|
|
114
|
+
window.sgrAPI.setMode = m => {
|
|
115
|
+
if(m === "f") {
|
|
116
|
+
window.sgrAPI.gameInfo[2].ga = "f";
|
|
117
|
+
window.sgrAPI.gameInfo[2].tea = true;
|
|
118
|
+
window.sgrAPI.toolFunctions.networkEngine.sendTeamSettingsChange(window.sgrAPI.gameInfo[2].tea);
|
|
119
|
+
} else {
|
|
120
|
+
window.sgrAPI.gameInfo[2].ga = "b";
|
|
121
|
+
}
|
|
122
|
+
window.sgrAPI.gameInfo[2].mo = m;
|
|
123
|
+
window.sgrAPI.menuFunctions.updatePlayers();
|
|
124
|
+
window.sgrAPI.toolFunctions.networkEngine.sendGAMO(window.sgrAPI.gameInfo[2].ga, window.sgrAPI.gameInfo[2].mo);
|
|
125
|
+
window.sgrAPI.menuFunctions.updateGameSettings();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Sets teams to true or false.
|
|
129
|
+
window.sgrAPI.setTeams = teams => {
|
|
130
|
+
if(window.sgrAPI.gameInfo[2].ga === "f") return;
|
|
131
|
+
|
|
132
|
+
window.sgrAPI.gameInfo[2].tea = teams;
|
|
133
|
+
window.sgrAPI.toolFunctions.networkEngine.sendTeamSettingsChange(teams);
|
|
134
|
+
window.sgrAPI.menuFunctions.updatePlayers();
|
|
135
|
+
window.sgrAPI.menuFunctions.updateGameSettings();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Loads a map object.
|
|
139
|
+
window.sgrAPI.loadMap = map => {
|
|
140
|
+
let mapContainer = document.getElementById("maploadwindowmapscontainer");
|
|
141
|
+
while(mapContainer.firstChild) {
|
|
142
|
+
mapContainer.firstChild.remove();
|
|
143
|
+
}
|
|
144
|
+
window.sgrAPI.mapLoader({maps: [map]});
|
|
145
|
+
mapContainer.firstChild.click();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Gets a map response object containing favorited maps. response.maps is an array of map objects.
|
|
149
|
+
// An offset of 0 gives you the first 32 maps and incrementing by 1 gives you the next 32.
|
|
150
|
+
window.sgrAPI.getFav = async offset => {
|
|
151
|
+
let response;
|
|
152
|
+
|
|
153
|
+
await $.post("https://bonk2.io/scripts/map_getfave.php", {
|
|
154
|
+
token: sgrAPI.token,
|
|
155
|
+
startingfrom: offset * 32
|
|
156
|
+
}).done(e => {
|
|
157
|
+
|
|
158
|
+
if (e.r != "success") console.log("Failed to load favorited maps.");
|
|
159
|
+
else response = e;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return response;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Favorites a map.
|
|
166
|
+
window.sgrAPI.fav = async id => {
|
|
167
|
+
await $.post("https://bonk2.io/scripts/map_fave.php", {
|
|
168
|
+
token: sgrAPI.token,
|
|
169
|
+
mapid: id,
|
|
170
|
+
action: "f"
|
|
171
|
+
}).fail(e => {
|
|
172
|
+
|
|
173
|
+
console.log("Failed to favorite map:" + e);
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Gets map array from playlist string (from Salama's playlist mod) assuming maps are favorited.
|
|
178
|
+
// https://greasyfork.org/en/scripts/439123-bonk-playlists
|
|
179
|
+
window.sgrAPI.fromPlaylist = async (playlist) => {
|
|
180
|
+
playlist = JSON.parse(playlist);
|
|
181
|
+
let bonk2MapIds = playlist.map(p => p.maps).flat();
|
|
182
|
+
let bonk1Maps = playlist.map(p => p.b1maps).flat();
|
|
183
|
+
|
|
184
|
+
let foundMaps = [];
|
|
185
|
+
for(i = 0; foundMaps.length < bonk2MapIds.length; i++) {
|
|
186
|
+
let maps = (await sgrAPI.getFav(i)).maps;
|
|
187
|
+
if(!maps || maps.length === 0) break;
|
|
188
|
+
for(map of maps) {
|
|
189
|
+
if(bonk2MapIds.find(id => id === map.id) !== undefined) {
|
|
190
|
+
foundMaps.push(map);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return [foundMaps, bonk1Maps].flat();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Left = 37, Up = 38, Right = 39, Down = 40, X = 88, Z = 90
|
|
199
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
|
|
200
|
+
window.sgrAPI.keyDown = keyCode => {
|
|
201
|
+
let event = document.createEvent("HTMLEvents")
|
|
202
|
+
event.initEvent("keydown", true, false);
|
|
203
|
+
event["keyCode"] = keyCode;
|
|
204
|
+
document.getElementById("gamerenderer").dispatchEvent(event);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
window.sgrAPI.keyUp = keyCode => {
|
|
208
|
+
let event = document.createEvent("HTMLEvents")
|
|
209
|
+
event.initEvent("keyup", true, false);
|
|
210
|
+
event["keyCode"] = keyCode;
|
|
211
|
+
document.getElementById("gamerenderer").dispatchEvent(event);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// This function is called every game tick.
|
|
215
|
+
window.sgrAPI.onTick = () => {};
|
|
216
|
+
|
|
217
|
+
// This function returns an input object specifying the player's inputs. The input parameter contains whatever the inputs normally are.
|
|
218
|
+
// Example of input object: { left: false, right: false, up: false, down: false, action: false, action2: false }
|
|
219
|
+
window.sgrAPI.onInput = input => input;
|
|
220
|
+
|
|
221
|
+
// This function is called every time a websocket message is received.
|
|
222
|
+
// Returns true or false. Return false if you want to ignore default behavior.
|
|
223
|
+
// More information here: https://github.com/UnmatchedBracket/DemystifyBonk/blob/main/Packets.md
|
|
224
|
+
// message is a string. For instance, chat messages always follow the format of 42[20,playerId,"message"].
|
|
225
|
+
window.sgrAPI.onReceive = message => true;
|
|
226
|
+
|
|
227
|
+
window.sgrAPI.onSend = message => true;
|
|
228
|
+
|
|
229
|
+
window.sgrAPI.send = message => {
|
|
230
|
+
window.sgrAPI.socket.send(message);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
//This function can be overwritten to spoof or get data from post requests.
|
|
234
|
+
window.sgrAPI.onPost = (url, input) => sgrAPI.oldPost(url, input).then((output, status) => {
|
|
235
|
+
return output;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
//This function is used with onPost for wrapping post responses in JQuery promises.
|
|
239
|
+
window.sgrAPI.postResponse = value => {
|
|
240
|
+
const deferred = $.Deferred();
|
|
241
|
+
deferred.resolve(value, 'success', { status: 200 });
|
|
242
|
+
return deferred.promise();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//onPost example. Changes username on login. Fake username is only visible for you.
|
|
246
|
+
/*
|
|
247
|
+
sgrAPI.onPost = (url, input) => sgrAPI.oldPost(url, input).then((output, status) => {
|
|
248
|
+
if(url.endsWith("login_auto.php") || url.endsWith("login_legacy.php")) {
|
|
249
|
+
output.username = "Fake username";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return output;
|
|
253
|
+
});
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
//Example of sending spoofed data for a post response without retrieving data from bonk servers.
|
|
257
|
+
/*
|
|
258
|
+
sgrAPI.onPost = (url, input) => {
|
|
259
|
+
if(url.endsWith("map_getfave.php")) {
|
|
260
|
+
return postResponse({
|
|
261
|
+
maps: [{
|
|
262
|
+
"id": 123,
|
|
263
|
+
"name": "Simple 1v1",
|
|
264
|
+
"authorname": "GudStrat",
|
|
265
|
+
"leveldata": "ILDuJAhZIawhiQEVgGkCqAmANgFwGMBxADxwEkARAMSwFlVyAlAZgDVYMWmBPATQAaqGAEsArhACiAVlTRgACwASAEwDqTACoqlAKQUqkwAKahJCAMIgAHCgTngGSKGGJklV0a-efSByAA5NjVpMT41AEYcAC0LSE0AQyJqUGiBAHoAN3Sc3JyodIB2PLyWLJLc4CIAWwA2FQBzIzkCcDo6AT4ScmpIAGdBJmqAIxZdPF9J7wAFAGofbO90gAYALzp1zbpwKd29-YPJh2RJaBP5f2ALUGp4JEpgAHlPQ69Ic+BPNk1iahZh2CQaJeUCUO6vHxOT6XahcSAGLAAFkQQA",
|
|
266
|
+
"publisheddate": "2020-05-05 16:59:52",
|
|
267
|
+
"vu": 71626,
|
|
268
|
+
"vd": 14821,
|
|
269
|
+
"remixname": "",
|
|
270
|
+
"remixauthor": "",
|
|
271
|
+
"remixdb": 1,
|
|
272
|
+
"remixid": 0,
|
|
273
|
+
}],
|
|
274
|
+
more: false,
|
|
275
|
+
r: "success",
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
//Makes a room and returns the room link.
|
|
282
|
+
window.sgrAPI.makeRoom = async (name, password, maxPlayers, minLevel, maxLevel, unlisted) => {
|
|
283
|
+
await sgrAPI.domContentLoaded;
|
|
284
|
+
while(true) {
|
|
285
|
+
document.getElementById("roomlistrefreshbutton").click();
|
|
286
|
+
document.getElementById("roomlistcreatewindowgamename").value = name;
|
|
287
|
+
document.getElementById("roomlistcreatewindowpassword").value = password;
|
|
288
|
+
document.getElementById("roomlistcreatewindowmaxplayers").value = maxPlayers;
|
|
289
|
+
document.getElementById("roomlistcreatewindowminlevel").value = minLevel;
|
|
290
|
+
document.getElementById("roomlistcreatewindowmaxlevel").value = maxLevel;
|
|
291
|
+
document.getElementById("roomlistcreatewindowunlistedcheckbox").checked = unlisted;
|
|
292
|
+
document.getElementById("roomlistcreatecreatebutton").click();
|
|
293
|
+
await new Promise(result => setTimeout(result, 500));
|
|
294
|
+
let connectStr = document.getElementById("sm_connectingWindow_text").innerText;
|
|
295
|
+
let connectVisibility = document.getElementById("sm_connectingContainer").style.visibility;
|
|
296
|
+
if(connectStr !== "Creating room...\nConnect error" && connectVisibility !== "hidden" && connectVisibility !== "") {
|
|
297
|
+
while(true) {
|
|
298
|
+
document.getElementById("newbonklobby_linkbutton").click();
|
|
299
|
+
await new Promise(result => setTimeout(result, 500));
|
|
300
|
+
let messages = document.getElementById("newbonklobby_chat_content").children;
|
|
301
|
+
if(messages.length > 2) {
|
|
302
|
+
return messages[messages.length-1].innerText.slice(2);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Joins room with a blank skin without updating UI or game state.
|
|
310
|
+
window.sgrAPI.shadowJoinRoom = async url => {
|
|
311
|
+
let match = url.match(/\/(\d+)([a-z]*)$/);
|
|
312
|
+
let id = match[1];
|
|
313
|
+
const bypass = match[2];
|
|
314
|
+
|
|
315
|
+
let address;
|
|
316
|
+
let server;
|
|
317
|
+
await $.post("https://bonk2.io/scripts/autojoin.php", {
|
|
318
|
+
joinID: id
|
|
319
|
+
}).done(e => {
|
|
320
|
+
|
|
321
|
+
if (e.r != "success") console.log("Failed to get room from ID.");
|
|
322
|
+
else {
|
|
323
|
+
address = e.address;
|
|
324
|
+
server = e.server;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
let response = await window.fetch(`https://${server}.bonk.io/socket.io/?EIO=3&transport=polling&t=${Date.now()}`);
|
|
329
|
+
sid = (await response.text()).match(/"sid"\s*:\s*"([^"]+)"/)[1];
|
|
330
|
+
|
|
331
|
+
//Query string parameters swapped to protect from spoofing.
|
|
332
|
+
let socket = new window.WebSocket(`wss://${server}.bonk.io/socket.io/?transport=websocket&sid=${sid}&EIO=3`);
|
|
333
|
+
socket.noSpoof = true;
|
|
334
|
+
|
|
335
|
+
socket.addEventListener("open", () => {
|
|
336
|
+
socket.send("5");
|
|
337
|
+
|
|
338
|
+
let data = {
|
|
339
|
+
joinID: address,
|
|
340
|
+
roomPassword: "",
|
|
341
|
+
guest: false,
|
|
342
|
+
dbid: 2,
|
|
343
|
+
version: 49,
|
|
344
|
+
peerID: Math.random().toString(36).substr(2, 10) + "v00000",
|
|
345
|
+
bypass: bypass,
|
|
346
|
+
token: sgrAPI.token,
|
|
347
|
+
avatar: {layers: [], bc: 0}
|
|
348
|
+
};
|
|
349
|
+
socket.send(`42[13,${JSON.stringify(data)}]`);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return socket;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if(!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
|
|
356
|
+
window.bonkCodeInjectors.push((code) => {
|
|
357
|
+
// Step functions
|
|
358
|
+
code = code.replace(
|
|
359
|
+
/[A-Za-z]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]={discs/,
|
|
360
|
+
match => "window.sgrAPI.state = arguments[0]; window.sgrAPI.onTick();" + match,
|
|
361
|
+
);
|
|
362
|
+
code = code.replace(
|
|
363
|
+
/=\[\];if\(\![A-Za-z0-9\$_]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\)\{/,
|
|
364
|
+
match => {
|
|
365
|
+
match = match.split(";");
|
|
366
|
+
return match[0] + ";window.sgrAPI.footballState = arguments[0]; window.sgrAPI.onTick();" + match[1];
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Set state
|
|
371
|
+
let stateCreationString = code.match(/[A-Za-z]\[...(\[[0-9]{1,4}\]){2}\]\(\[\{/)[0];
|
|
372
|
+
let stateCreationStringIndex = stateCreationString.match(/[0-9]{1,4}/g);
|
|
373
|
+
stateCreationStringIndex = stateCreationStringIndex[stateCreationStringIndex.length - 1];
|
|
374
|
+
let stateCreation = code.match(`[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[${stateCreationStringIndex}\\]\\].+?(?=;);`)[0];
|
|
375
|
+
stateCreationString = stateCreation.split(']')[0] + "]";
|
|
376
|
+
code = code.replace(
|
|
377
|
+
/\* 999\),[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],null,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],true\);/,
|
|
378
|
+
match => match + `
|
|
379
|
+
if(window.sgrAPI.nextScores) {
|
|
380
|
+
${stateCreationString}.scores = sgrAPI.nextScores;
|
|
381
|
+
}
|
|
382
|
+
window.sgrAPI.nextScores = undefined;
|
|
383
|
+
`,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
//Input Handler
|
|
387
|
+
//Credit to gmmaker for regex: https://github.com/SneezingCactus/gmmaker
|
|
388
|
+
//Original regex: /Date.{0,100}new ([^\(]+).{0,100}\$\(document/
|
|
389
|
+
code = code.replace(
|
|
390
|
+
/Date.{0,100}[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\];[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=new [^\(]+\(\);/,
|
|
391
|
+
match => {
|
|
392
|
+
let inputFunc = match.match(/([A-Za-z0-9\$_]{3}\[[0-9]{1,3}\])=new ([^\(]+)\(\);/);
|
|
393
|
+
return match.replace(inputFunc[0], `${inputFunc[0]} window.sgrAPI.input = ${inputFunc[1]};` +
|
|
394
|
+
`window.sgrAPI.oldGetInputs = window.sgrAPI.input.getInputs;`+
|
|
395
|
+
`window.sgrAPI.input.getInputs = () => window.sgrAPI.onInput(window.sgrAPI.oldGetInputs());`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Remove round limit (elemento pode não existir no menu / antes do lobby)
|
|
401
|
+
const roundsInputEl = document.getElementById('newbonklobby_roundsinput');
|
|
402
|
+
if (roundsInputEl) roundsInputEl.removeAttribute("maxlength");
|
|
403
|
+
code = code.replace(
|
|
404
|
+
/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]=Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(1,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\),9\);/,
|
|
405
|
+
"",
|
|
406
|
+
);
|
|
407
|
+
code = code.replace(
|
|
408
|
+
/[A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]=parseInt\([A-Za-z0-9\$_]{3}(\[0\]){2}\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\]\)\;/,
|
|
409
|
+
match => {
|
|
410
|
+
let roundValVar = match.split("=")[0];
|
|
411
|
+
return `${roundValVar}=parseInt(document.getElementById('newbonklobby_roundsinput').value); if(isNaN(${roundValVar}) || ${roundValVar} <= 0) {return;}`;
|
|
412
|
+
},
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// Map loader
|
|
416
|
+
let mapLoader = code.match(/maploadwindowsearchinput.{0,200}else if\([A-Za-z0-9\$_]{3}\[0\]\[0\]\[[A-Za-z0-9\$_]{3}\[[0-9]+\][[0-9]+\]\] == [A-Za-z0-9\$_]{3}\.[A-Za-z0-9\$_]{3}\([0-9]+\)\)\{[A-Za-z0-9\$_]{3}\([A-Za-z0-9\$_]{3}\[0\]\[0\]\);[A-Za-z0-9\$_]{3}\[[0-9]+\]=[A-Za-z0-9\$_]{3}\[0\]\[0\]\[[A-Za-z0-9\$_]{3}\[[0-9]+\]\[[0-9]+\]\];\}\}\)/g)[0].match(/[A-Za-z0-9\$_]{3}\([A-Za-z0-9\$_]{3}\[0\]\[0\]\);/)[0].slice(0, 3);
|
|
417
|
+
code = code.replace(
|
|
418
|
+
`function ${mapLoader}`,
|
|
419
|
+
`window.sgrAPI.mapLoader=${mapLoader};function ${mapLoader}`
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
//Function for all callbacks
|
|
423
|
+
window.sgrAPI.bonkCallbacks = {};
|
|
424
|
+
let callbacks = [...code.match(/[A-Za-z0-9\$_]{3}\(\.\.\./g)];
|
|
425
|
+
for(let callback of callbacks) {
|
|
426
|
+
code = code.replace(
|
|
427
|
+
`function ${callback}`,
|
|
428
|
+
`window.sgrAPI.bonkCallbacks["${callback.split("(")[0]}"] = ${callback.split("(")[0]};` + `function ${callback}`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Useful functions
|
|
433
|
+
code = code.replace(
|
|
434
|
+
/== 13\){...\(\);}}/,
|
|
435
|
+
match => match + "window.sgrAPI.menuFunctions = this;",
|
|
436
|
+
);
|
|
437
|
+
code = code.replace(
|
|
438
|
+
/=new [A-Za-z0-9\$_]{1,3}\(this,[A-Za-z0-9\$_]{1,3}\[0\]\[0\],[A-Za-z0-9\$_]{1,3}\[0\]\[1\]\);/,
|
|
439
|
+
match => match + "window.sgrAPI.toolFunctions = this;",
|
|
440
|
+
);
|
|
441
|
+
code = code.replace(
|
|
442
|
+
/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=\{id:-1,element:null\};/,
|
|
443
|
+
match => match + "window.sgrAPI.gameInfo = arguments;",
|
|
444
|
+
);
|
|
445
|
+
code = code.replace("{a:0.0};", "{a:0.0};window.sgrAPI.stateFunctions = this;");
|
|
446
|
+
code = code.replace(
|
|
447
|
+
"newbonklobby_votewindow_close",
|
|
448
|
+
"window.sgrAPI.players = arguments[1]; newbonklobby_votewindow_close",
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
sgrAPI.injectedResolve();
|
|
452
|
+
console.log("sgrAPI run");
|
|
453
|
+
return code;
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
window.sgrAPI.originalSend = window.WebSocket.prototype.send;
|
|
457
|
+
window.WebSocket.prototype.send = function(args) {
|
|
458
|
+
let sendFilter;
|
|
459
|
+
|
|
460
|
+
if (this.url.includes("socket.io/?EIO=3&transport=websocket&sid=") && !this.noSpoof) {
|
|
461
|
+
if (!this.injectedAPI) {
|
|
462
|
+
window.sgrAPI.socket = this;
|
|
463
|
+
this.injectedAPI = true;
|
|
464
|
+
|
|
465
|
+
window.sgrAPI.originalReceive = this.onmessage;
|
|
466
|
+
this.onmessage = function(args) {
|
|
467
|
+
const onRecv = window.sgrAPI.onReceive;
|
|
468
|
+
const receiveFilter = typeof onRecv === 'function' ? onRecv(args.data) : true;
|
|
469
|
+
if(receiveFilter === undefined || receiveFilter === true) return window.sgrAPI.originalReceive.call(this, args);
|
|
470
|
+
else return;
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
sendFilter = window.sgrAPI.onSend(args);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if(sendFilter === undefined || sendFilter === true) return window.sgrAPI.originalSend.call(this, args);
|
|
478
|
+
else return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
window.sgrAPI.token = null;
|
|
482
|
+
window.sgrAPI.oldPost = () => {};
|
|
483
|
+
window.sgrAPI.domContentLoadedResolve;
|
|
484
|
+
window.sgrAPI.domContentLoaded = new Promise(res => sgrAPI.domContentLoadedResolve = () => res());
|
|
485
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
486
|
+
sgrAPI.domContentLoadedResolve();
|
|
487
|
+
|
|
488
|
+
if (typeof $ === 'undefined' || !$.post) return;
|
|
489
|
+
let olderPost = $.post;
|
|
490
|
+
sgrAPI.oldPost = function() {
|
|
491
|
+
return olderPost.call($, ...arguments).then((output, status) => {
|
|
492
|
+
if(arguments[0].endsWith("login_auto.php") || arguments[0].endsWith("login_legacy.php")) {
|
|
493
|
+
sgrAPI.token = output.token;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return output;
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
$.post = function(url, input) {
|
|
501
|
+
const output = sgrAPI.onPost(url, input);
|
|
502
|
+
if(output === undefined) return sgrAPI.oldPost(...arguments);
|
|
503
|
+
|
|
504
|
+
return output;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Só registrar listeners se o elemento existir (no menu não existe; evita crash na criação da sala)
|
|
508
|
+
const roundsInput = document.getElementById("newbonklobby_roundsinput");
|
|
509
|
+
if (roundsInput) {
|
|
510
|
+
roundsInput.addEventListener("focus", e => {
|
|
511
|
+
e.target.value = "";
|
|
512
|
+
});
|
|
513
|
+
roundsInput.addEventListener("blur", e => {
|
|
514
|
+
if (e.target.value == "") {
|
|
515
|
+
// bonkHost é de outro mod; sem ele usamos fallback para não quebrar
|
|
516
|
+
if (window.bonkHost && window.bonkHost.toolFunctions && typeof window.bonkHost.toolFunctions.getGameSettings === "function") {
|
|
517
|
+
e.target.value = window.bonkHost.toolFunctions.getGameSettings().wl;
|
|
518
|
+
} else {
|
|
519
|
+
e.target.value = "3";
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
console.log("sgrAPI loaded");
|
package/dist/bot.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bonktools_1 = require("bonktools");
|
|
4
|
+
require("dotenv").config();
|
|
5
|
+
const bot = (0, bonktools_1.createBot)({
|
|
6
|
+
account: {
|
|
7
|
+
username: "FUTHERO BOT",
|
|
8
|
+
password: process.env.BOT_PASSWORD,
|
|
9
|
+
guest: false,
|
|
10
|
+
},
|
|
11
|
+
PROTOCOL_VERSION: 49,
|
|
12
|
+
server: "b2brazil1",
|
|
13
|
+
logLevel: bonktools_1.LOG_LEVELS.WARN,
|
|
14
|
+
});
|
|
15
|
+
exports.default = bot;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.browserManager = exports.BrowserManager = void 0;
|
|
7
|
+
const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
|
|
8
|
+
/**
|
|
9
|
+
* Browser manager to keep a Bonk.io instance open.
|
|
10
|
+
* Bonk.io requires a real browser window for some actions (GAME_START, CHANGE_OTHER_TEAM)
|
|
11
|
+
* to work correctly due to internal security rules.
|
|
12
|
+
*/
|
|
13
|
+
class BrowserManager {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.browser = null;
|
|
16
|
+
this.page = null;
|
|
17
|
+
this.roomUrl = null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Starts the browser and navigates to the bot room.
|
|
21
|
+
* IMPORTANT: The browser does NOT log in, it only opens the room URL.
|
|
22
|
+
* The bot (bonktools) is already connected via WebSocket on the same account.
|
|
23
|
+
* The browser is only used to keep the required presence for game start.
|
|
24
|
+
*
|
|
25
|
+
* @param roomUrl - Room URL (https://bonk.io/...)
|
|
26
|
+
* @param options - Configuration options
|
|
27
|
+
*/
|
|
28
|
+
async launch(roomUrl, options = {}) {
|
|
29
|
+
try {
|
|
30
|
+
this.roomUrl = roomUrl;
|
|
31
|
+
const headless = options.headless ?? process.env.BROWSER_HEADLESS === "true";
|
|
32
|
+
const envPath = process.env.CHROME_PATH?.trim();
|
|
33
|
+
const executablePath = (options.executablePath?.trim() || envPath) || this.getDefaultChromePath();
|
|
34
|
+
console.log("[Browser] Starting browser...");
|
|
35
|
+
console.log(`[Browser] Path: ${executablePath}`);
|
|
36
|
+
console.log(`[Browser] Headless: ${headless}`);
|
|
37
|
+
console.log("[Browser] NOTE: Browser will not log in - it only opens the room as a visitor");
|
|
38
|
+
this.browser = await puppeteer_core_1.default.launch({
|
|
39
|
+
executablePath,
|
|
40
|
+
headless,
|
|
41
|
+
args: [
|
|
42
|
+
"--no-sandbox",
|
|
43
|
+
"--disable-setuid-sandbox",
|
|
44
|
+
"--disable-dev-shm-usage",
|
|
45
|
+
"--disable-web-security",
|
|
46
|
+
"--mute-audio",
|
|
47
|
+
"--disable-blink-features=AutomationControlled", // Evita detecção de automação
|
|
48
|
+
],
|
|
49
|
+
defaultViewport: {
|
|
50
|
+
width: 1280,
|
|
51
|
+
height: 720,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
this.page = await this.browser.newPage();
|
|
55
|
+
// Configure user agent to look more natural
|
|
56
|
+
await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
57
|
+
console.log(`[Browser] Navigating to: ${roomUrl}`);
|
|
58
|
+
// Navigate directly to the room (WITHOUT logging in)
|
|
59
|
+
await this.page.goto(roomUrl, {
|
|
60
|
+
waitUntil: "domcontentloaded",
|
|
61
|
+
timeout: 30000,
|
|
62
|
+
});
|
|
63
|
+
// Wait for the game iframe to load
|
|
64
|
+
try {
|
|
65
|
+
await this.page.waitForSelector("#maingameframe", { timeout: 10000 });
|
|
66
|
+
console.log("[Browser] ✅ Browser connected to room!");
|
|
67
|
+
console.log("[Browser] Browser is only observing (not logged in)");
|
|
68
|
+
console.log("[Browser] Bot (bonktools) controls everything via WebSocket");
|
|
69
|
+
console.log("[Browser] ⚠️ KEEP THIS BROWSER OPEN for game start to work!");
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.warn("[Browser] ⚠️ Game iframe did not load, continuing anyway...");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("[Browser] ❌ Error while starting browser:", error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Closes the browser
|
|
82
|
+
*/
|
|
83
|
+
async close() {
|
|
84
|
+
if (this.browser) {
|
|
85
|
+
console.log("[Browser] Closing browser...");
|
|
86
|
+
await this.browser.close();
|
|
87
|
+
this.browser = null;
|
|
88
|
+
this.page = null;
|
|
89
|
+
console.log("[Browser] Browser closed.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Checks if the browser is active
|
|
94
|
+
*/
|
|
95
|
+
isActive() {
|
|
96
|
+
return this.browser !== null && this.browser.connected;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns the default Chrome path for each OS
|
|
100
|
+
*/
|
|
101
|
+
getDefaultChromePath() {
|
|
102
|
+
const platform = process.platform;
|
|
103
|
+
switch (platform) {
|
|
104
|
+
case "darwin": // macOS
|
|
105
|
+
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
106
|
+
case "win32": // Windows
|
|
107
|
+
return "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
|
|
108
|
+
case "linux":
|
|
109
|
+
return "/usr/bin/google-chrome";
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`[Browser] Unsupported operating system: ${platform}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns the current room URL
|
|
116
|
+
*/
|
|
117
|
+
getRoomUrl() {
|
|
118
|
+
return this.roomUrl;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Executes JavaScript in the browser page (for debugging)
|
|
122
|
+
*/
|
|
123
|
+
async evaluate(script) {
|
|
124
|
+
if (!this.page) {
|
|
125
|
+
console.warn("[Browser] Browser is not active.");
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const result = await this.page.evaluate(script);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("[Browser] Error while executing script:", error);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Takes a screenshot of the page (for debugging)
|
|
139
|
+
*/
|
|
140
|
+
async screenshot(path) {
|
|
141
|
+
if (!this.page) {
|
|
142
|
+
console.warn("[Browser] Browser is not active.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await this.page.screenshot({ path, fullPage: false });
|
|
147
|
+
console.log(`[Browser] Screenshot saved to: ${path}`);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error("[Browser] Error while taking screenshot:", error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.BrowserManager = BrowserManager;
|
|
155
|
+
// Singleton instance
|
|
156
|
+
exports.browserManager = new BrowserManager();
|