├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── account.js ├── agario-client.js ├── examples ├── auth_token.js ├── basic.js ├── multiple.js └── socks.js ├── package.json ├── packet.js └── servers.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 07.05.2016 ## 2 | Agar developers made completely new code for game. Now code runs inside virtual machine that I can not reverse-engineer due to lack of knowledge and experience. 3 | Unfortunately its time to say goodbye to `agario-client`. 4 | Initial idea of `agario-client` was for me to learn websockets, binary protocols, reverse-engineering, github and have fun overall. I believe that I achieved all that. 5 | Thanks to all, it was great time! 6 | 7 | ## 01.04.2016 ## 8 | Protocol changes: 9 | - Agar changed auth packet, check [issue #129](https://github.com/pulviscriptor/agario-client/issues/129) 10 | 11 | Code changes due to [PR #130](https://github.com/pulviscriptor/agario-client/pull/130) 12 | - `client.auth_provider` is not used anymore 13 | - Google+ tokens support is unknown at the moment 14 | 15 | ## 21.03.2016 ## 16 | - Event `client.on.getLogin` added 17 | 18 | ## 19.03.2016 ## 19 | Code changes: 20 | - Event `client.on.leaderBoardUpdate` arguments changed from 21 | `old_leaders_ids, new_leaders_ids` 22 | to 23 | `old_highlights, new_highlights, old_names, new_names` 24 | as fix for changed packet ID 49 (fixed by [kosak](https://github.com/kosak)) 25 | 26 | ## 18.03.2016 ## 27 | Protocol changes: 28 | - Initial key (pakect ID 255) changed key from 2200049715 to 154669603 29 | 30 | ## 17.03.2016 ## 31 | Protocol changes: 32 | - New packet ID `104` that forces client to call `window.logout()` to logout from account 33 | 34 | Code changes: 35 | - New event `client.on.logoutRequest` for logout packet ID `104` 36 | 37 | ## 28.02.2016 ## 38 | Protocol changes: 39 | - New packet ID `18` that commands to delete all known balls from memory. 40 | 41 | Code changes: 42 | - New destroy ball reason `server-forced` when server commands to delete all balls. 43 | 44 | ## 16.02.2016 ## 45 | Now agar.io sends random "virtual" size map as protection from bots. 46 | New sizes are refreshing in `client.on.mapSizeLoad` event. 47 | When you re-spawning you should delete old balls from memory because their coordinates will be invalid. Not adding this to client's code because it can be changed by agar any time. 48 | 49 | ## 28.01.2016 ## 50 | Now agar.io server allows only 3 connections from single IP instead of 5. 51 | 52 | ## 14.01.2016 ## 53 | Added support for the debug line (added by [henopied](https://github.com/henopied)) 54 | Code changes: 55 | 56 | - `on.debugLine(line_x, line_y)` added, the server sometimes sends a line for the client to render from your ball to the point though don't expect to see it. 57 | 58 | ## 13.01.2016 ## 59 | Code changes: 60 | - `client.local_address` added. Local interface to bind to for network connections (IP address of interface) 61 | - `AgarioClient.servers` `opt.local_address` added. Local interface to bind to for network connections (IP address of interface) 62 | 63 | ## 12.01.2016 ## 64 | Code changes: 65 | - `client.headers` added for customizing WebSocket connection headers. 66 | 67 | ## 07.01.2016 ## 68 | Code changes: 69 | - `client.on.packetError(packet, err, preventCrash)` added. Check [issue #46](https://github.com/pulviscriptor/agario-client/issues/46#issuecomment-169764771) 70 | 71 | Thanks to [DrTheGoat](https://github.com/DrTheGoat) for helping with getting corrupted packet. 72 | 73 | ## 25.12.2015 ## 74 | agar changes: 75 | - Facebook key is deprecated. Now tokens used. 76 | 77 | Code changes: 78 | - Example `examples/auth_token.js` added demonstrating requesting facebook token 79 | - `client.facebook_key` marked as deprecated 80 | - `client.auth_token` added 81 | - `client.auth_provider` added 82 | - `AgarioClient.Account` added 83 | - `AgarioClient.Account.requestFBToken()` added (with [Endromede](https://github.com/Endromede)'s help) 84 | 85 | This update would not been possible without [Endromede](https://github.com/Endromede) who reverse-engineered new tokens system. 86 | 87 | ## 29.11.2015 ## 88 | Example `examples/multiple.js` added demonstrating 5 clients connecting to a party server from one script 89 | 90 | ## 28.11.2015 ## 91 | Protocol changes: 92 | 93 | - Initial key (pakect ID 255) changed key from 154669603 (0x9381223) to 2200049715 (0x83221833) 94 | 95 | ## 30.10.2015 ## 96 | Protocol changes: 97 | 98 | - Premium skins/colors added 99 | - Previously reserved byte in packet ID 16 part 2 is now used for some variable of ball and indicator of premium skin/color 100 | 101 | New variables/skins info will be added to agario-client later 102 | 103 | ## 28.10.2015 ## 104 | Code changes: 105 | 106 | - `example.js` moved to `./examples/` 107 | 108 | Thanks to [henopied](https://github.com/henopied) who showed me how to add SOCKS/Proxy support now we have: 109 | 110 | - `AgarioClient.agent` added 111 | - `AgarioClient.servers` `opt.agent` added 112 | - `AgarioClient.servers` `opt.resolve` added 113 | - `AgarioClient.servers` `opt.ip` added 114 | - `./examples/socks.js` added showing how to use SOCKS 115 | 116 | Test it using `node ./node_modules/agario-client/examples/socks.js` 117 | 118 | ## 10.10.2015 ## 119 | Code changes: 120 | 121 | - `Client.spectateModeToggle()` added (by [jashman](https://github.com/jashman)) 122 | 123 | ## 29.09.2015 ## 124 | Client changes: 125 | 126 | - Facebook key is now stored in `JSON.parse(localStorage.loginCache3).authToken` 127 | 128 | ## 20.09.2015 ## 129 | Code changes: 130 | 131 | Protection from coding incidents added. 132 | Next functions now can be called before connection established: 133 | 134 | - `client.spawn()` 135 | - `client.spectate()` 136 | - `client.moveTo()` 137 | - `client.split()` 138 | - `client.eject()` 139 | 140 | They will return `false` if packet was not sent and `true` on success. 141 | 142 | ## 17.09.2015 ## 143 | Code changes: 144 | 145 | - `AgarioClient.Ball` added 146 | - Added stability to `spawn()`. By default client will try to spawn 25 times before disconnect 147 | - `on.connected` event is now emited without 2sec delay 148 | - First `spawn()` after connect is now much faster 149 | - Config variable `client.spawn_attempts` added 150 | - Config variable `client.spawn_interval` added 151 | - `example.js` updated with custom events/variables example 152 | 153 | ## 11.09.2015 ## 154 | Code changes: 155 | 156 | - `ball.mass` added 157 | 158 | ## 13.08.2015 ## 159 | Protocol changes: 160 | 161 | - Move packet id `16` coordinates changed from `DoubleLE` to `Int32LE` 162 | 163 | ## 31.07.2015 ## 164 | Code changes: 165 | Deprecated on `04.06.2015` functions removed: 166 | 167 | - function `Client.off` removed 168 | - function `Client.offAll` removed 169 | - function `Client.emitEvent` removed 170 | 171 | ## 23.07.2015 ## 172 | Code changes: 173 | 174 | `var AgarioClient = require('agario-client');` 175 | 176 | - `AgarioClient.servers.getFFAServer` added 177 | - `AgarioClient.servers.getTeamsServer` added 178 | - `AgarioClient.servers.getExperimentalServer` added 179 | - `AgarioClient.servers.getPartyServer` added 180 | - `AgarioClient.servers.createParty` added 181 | - `example.js` is now using `AgarioClient.servers.getFFAServer` 182 | 183 | ## 18.07.2015 ## 184 | Protocol changes: 185 | 186 | - Initial packet id `254` sends `05` instead `04` which forces server to use new protocol 187 | - Tick packet id `16` part 2 now uses `SInt32LE` for coordinates 188 | 189 | ## 17.07.2015 ## 190 | Code changes: 191 | 192 | - `Client.facebook_key` added to login with facebook key 193 | - `Client.on.experienceUpdate(level, current_exp, need_exp)` experience information update (if logined) 194 | 195 | ## 24.06.2015 ## 196 | Protocol changes: 197 | 198 | - Initial packet id 255 changed from `0xFF33189283` to `0xFF23123809` 199 | - Server will disconnect you if you send old packet 200 | 201 | ## 23.06.2015 ## 202 | Protocol changes: 203 | 204 | - Initial packet id 255 changed from `0xFF29282828` to `0xFF33189283` 205 | - Server will disconnect you if you send old packet 206 | 207 | ## 21.06.2015 ## 208 | Today is a bad day 209 | Protocol changes: 210 | 211 | - Now website sends server and server's key without which you will not be accepted by server 212 | - New packet id 80 that used for sending server's key to server 213 | 214 | Code changes: 215 | 216 | - `Client.connect(server)` changed to `Client.connect(server, key)` 217 | - Initial packet id 255 changed to simulate original code 218 | - Initial packets 254 and 80 added 219 | - `connected` event is now calling with 2000ms delay otherwise server will ignore spawn packet 220 | 221 | ## 13.06.2015 ## 222 | Code changes: 223 | 224 | - `Client.spectate()` added (by [RouxRC](https://github.com/RouxRC)) 225 | - `Client.on.spectateFieldUpdate(cord_x, cord_y, zoom_level)` added 226 | 227 | ## 08.06.2015 ## 228 | Protocol changes: 229 | 230 | - New packet id 240 that moves offset (why, agar? what for?) 231 | 232 | Code changes: 233 | 234 | - New packet management architecture 235 | - [buffer-dataview](https://github.com/TooTallNate/node-buffer-dataview) not used anymore 236 | 237 | ## 07.06.2015 ## 238 | `agario-client` added to [NPM](https://www.npmjs.com/package/agario-client) 239 | 240 | ## 06.06.2015 ## 241 | Code changes: 242 | 243 | - `Client.score` added (by [GeoffreyFrogeye](https://github.com/GeoffreyFrogeye)) 244 | - `Client.on.scoreUpdate(old_score, new_score)` added (by [GeoffreyFrogeye](https://github.com/GeoffreyFrogeye)) 245 | 246 | ## 04.06.2015 ## 247 | Code changes: 248 | 249 | - `Ball.color` is now working (fixed by [GeoffreyFrogeye](https://github.com/GeoffreyFrogeye)) 250 | - New events methods (improved by [GeoffreyFrogeye](https://github.com/GeoffreyFrogeye)) 251 | - Deprecated property `Ball.is_virus` completely removed 252 | - Deprecated property `Ball.is_mine` completely removed 253 | - `.off()` marked as deprecated and replaced with `.removeListener()` 254 | - `.offAll()` marked as deprecated and replaced with `.removeAllListeners()` 255 | - `.emitEvent()` marked as deprecated and replaced with `.emit()` 256 | - `Client.server` added 257 | 258 | ## 01.06.2015 ## 259 | Protocol changes: 260 | 261 | - `ball` coordinates changed from 32bit float to 16bit signed integer 262 | - `ball` size changed from 32bit float to 16bit signed integer 263 | - packet ID 16 part 3 changed from list of visible balls to list of destroyed balls 264 | - two bits between 2 and 3 part of packet 16 is not sent anymore 265 | 266 | ## 18.05.2015 ## 267 | Now `example.js` will automatically request server and connect to it. 268 | 269 | ## 12.05.2015 ## 270 | Protocol changes: 271 | 272 | - `ball` coordinates changed from 64bit float to 32bit float 273 | - `ball` size changed from 64bit float to 32bit float 274 | - color is now generating on server and sent to client 275 | - new packet 72 that not used in original code 276 | - new packet 50 used for teams scores in teams mode 277 | 278 | Code changes: 279 | 280 | - color is now stored in `Ball.color` 281 | - added empty processor for packet ID 72 (packet not used in original code) 282 | - added `Client.teams_scores` property for teams mode 283 | - added `Client.on.teamsScoresUpdate(old_scores, new_scores)` event for teams mode 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agario-client # 2 | Node.js client for [agar.io](http://agar.io) with API. 3 | 4 | # Outdated! # 5 | Agar developers made completely new code for game. Now code runs inside virtual machine that I can not reverse-engineer due to lack of knowledge and experience. 6 | Unfortunately its time to say goodbye to `agario-client`. 7 | Initial idea of `agario-client` was for me to learn websockets, binary protocols, reverse-engineering, github and have fun overall. I believe that I achieved all that. 8 | Thanks to all, it was great time! 9 | 10 | ## Instructions ## 11 | - Install [Node.js](https://nodejs.org/) 12 | - Install client using `npm install agario-client` (ignore `python` errors) 13 | - Run `node ./node_modules/agario-client/examples/basic.js` (for testing purpose) 14 | - If it works, you're ready to look at API and code 15 | 16 | # API # 17 | There are two types of object that have API: 18 | 19 | - **Client** - thing that connects to [agar.io](http://agar.io) server and talks to it. If you want to spawn and control your `Ball`, you need to talk with `Client` 20 | - **Ball** - thing that `Client` creates. Everything in game is `Balls` (viruses/food/players...). You can't control `Balls` objects, only observe what they do. 21 | 22 | Both objects have same methods for events from [events.EventEmitter](https://nodejs.org/api/events.html): 23 | 24 | - `.on('eventName', callback)` attach listener to event 25 | - `.once('eventName', callback)` attach listener to event but execute only once 26 | - `.removeListener('eventName', callback)` remove listener from event 27 | - `.removeAllListeners('eventName')` remove all listeners from event 28 | - `.emit('eventName', p1, p2...)` emit your own event 29 | - check more in [documentation](https://nodejs.org/api/events.html) 30 | 31 | # Client API # 32 | var AgarioClient = require('agario-client'); 33 | var client = new AgarioClient(client_name); 34 | *client_name* is string for client that will be used for logging (if you enable it). It's not your ball name. 35 | 36 | ## Client properties ## 37 | Properties that you can change: 38 | 39 | - `client.debug` debug level. 0-5. 0 is completely silent. 5 is super verbose. **Default: 1** 40 | - `client.server` address that was used in `client.connect()` call 41 | - `client.key` key that was used in `client.connect()` call 42 | - `client.auth_token` token to login. See how to get token in [additional info](#auth-token). 43 | - `client.agent` agent to use for connection. Check [additional info](#socksproxy-support). 44 | - `client.local_address` local interface to bind to for network connections (IP address of interface) 45 | - `client.headers` object with headers for WebSocket connection. **Default: {'Origin':'http://agar.io'}** 46 | - `client.inactive_destroy` time in ms for how long ball will live in memory after his last known action (if player exit from game or ball eaten outside our field of view, we will not know it since server sends action only about field that you see. Original code `destroy()` `Ball` when he `disappear` from field of view. You can do that in `client.on('ballDisappear')` if you want it for some reason). **Default: 5\*60\*1000** (5 minutes) 47 | - `client.inactive_check` time in ms for time interval that search and destroy inactive `Balls`. **Default: 10\*1000** (10 seconds) 48 | - `client.spawn_attempts` how much we need try spawn before disconnect (made for unstable spawn on official servers). **Default: 25** 49 | - `client.spawn_interval` time in ms between spawn attempts. **Default: 200** 50 | 51 | Properties that better not to change or you can break something: 52 | 53 | - `client.balls` object with all `Balls` that `client` knows about. Access `Ball` like `client.balls[ball_id]` 54 | - `client.my_balls` array of alive `Ball`'s IDs that `client` owns and can control. 55 | - `client.score` personal score since spawn 56 | - `client.leaders` array of leader's `Balls` IDs in FFA mode. (player can have lots of `Balls`, but sever sends only one ID of one `Ball`) 57 | - `client.teams_scores` array of team's scores in teams mode 58 | - `client.client_name` name that you have set for `client` (not nickname) 59 | - `client.tick_counter` number of *tick* packets received (i call them ticks because they contains information about eating/movement/size/disappear... of `Balls`) 60 | 61 | ## Client methods ## 62 | - `client.connect(server, key)` connect to [agar.io](http://agar.io) server. Check [Servers](#servers) part in this readme to see how to get server IP and key. **ProTip:** each server have few rooms (if it is not party), so you may need connect few times before you will get in room that you want (but you need new key each time and [agar.io](http://agar.io) can ban your IP for flooding with requests). You can look `client.once('leaderBoardUpdate')` to know if you're connected to correct room 63 | - `client.disconnect()` disconnect from server 64 | - `client.spawn(name)` will spawn `Ball` with nickname. `client.on('myNewBall')` will be called when server sends our `Ball` info. Will return `false` if connection is not established. 65 | - `client.spectate()` will activate spectating mode. Look at `client.on('spectateFieldUpdate')` for FOV updates. Will return `false` if connection is not established. 66 | - `client.spectateModeToggle()` switching spectate mode in spectating mode (**Q** key in official client). Use `client.moveTo()` to move your "camera" around. Look at `client.on('spectateFieldUpdate')` for movement updates. Will return `false` if connection is not established. 67 | - `client.moveTo(x,y)` send move command. `x` and `y` is numbers where you want to move. Coordinates (size) of map you can get in `client.on('mapSizeLoad')`. Your `Balls` will move to coordinates you specified until you send new coordinates to move. Original source code do this every **100ms** (10 times in second) and before split and eject. Will return `false` if connection is not established. 68 | - `client.split()` will split your `Balls` in two. `Ball` will be ejected in last direction that you sent with `client.moveTo()`. `client.on('myNewBall')` will be called when server sends our `Balls` info. Will return `false` if connection is not established. 69 | - `client.eject()` will eject some mass from your `Balls`. Mass will be ejected in last direction that you sent with `client.moveTo()`. Ejected mass is `Ball` too (but we don't own them). So `client.on('ballAppear')` will be called when server sends ejected mass `Balls` info. Will return `false` if connection is not established. 70 | 71 | ## Client events ## 72 | In this list `on.eventName(param1, param2)` means you need to do `client.on('eventName', function(param1, param2) { ... })` 73 | 74 | - `on.connecting()` connecting to server 75 | - `on.connected()` connected to server and ready to spawn 76 | - `on.connectionError(err)` connection error 77 | - `on.disconnect()` disconnected 78 | - `on.message(packet)` new packet received from server (check `packet.js`) 79 | - `on.myNewBall(ball_id)` my new `Ball` created (spawn/split/explode...) 80 | - `on.somebodyAteSomething(eater_id, eaten_id)` somebody ate something 81 | - `on.scoreUpdate(old_score, new_score)` personal score updated 82 | - `on.leaderBoardUpdate(old_highlights, new_highlights, old_names, new_names)` leaders update in FFA mode. `names` is array of nicknames of leaders and `highlights` is array of numbers `0` or `1` corresponded to names where `1` means nickname should be highlighted in leader board (for example your nickname) 83 | - `on.teamsScoresUpdate(old_scores, new_scores)` array of teams scores update in teams mode 84 | - `on.mapSizeLoad(min_x, min_y, max_x, max_y)` map size update (as update 16.02.2016 this called then new virtual size of map received) 85 | - `on.reset()` when we delete all `Balls` and stop timers (connection lost?) 86 | - `on.winner(ball_id)` somebody won and server going for restart 87 | - `on.ballAction(ball_id, coordinate_x, coordinate_y, size, is_virus, nick)` some action about some `Ball` 88 | - `on.ballAppear(ball_id)` `Ball` appear on "screen" (field of view) 89 | - `on.ballDisappear(ball_id)` `Ball` disappear from "screen" (field of view) 90 | - `on.ballDestroy(ball_id, reason)` `Ball` deleted (check [reasons](#ball-destroy-reasons-list) at the bottom of this document) 91 | - `on.mineBallDestroy(ball_id, reason)` mine (your) `Ball` deleted (check reasons at the bottom of this document) 92 | - `on.lostMyBalls()` all mine `Balls` destroyed/eaten 93 | - `on.merge(destroyed_ball_id)` mine two `Balls` connects into one 94 | - `on.ballMove(ball_id, old_x, old_y, new_x, new_y)` `Ball` move 95 | - `on.ballResize(ball_id, old_size, new_size)` `Ball` resize 96 | - `on.ballRename(ball_id, old_name, new_name)` `Ball` set name/change name/we discover name 97 | - `on.ballUpdate(ball_id, old_update_time, new_update_time)` new data about ball received 98 | - `on.spectateFieldUpdate(cord_x, cord_y, zoom_level)` coordinates of field of view in `client.spectate()` mode 99 | - `on.experienceUpdate(level, current_exp, need_exp)` experience information update (if logined with [auth token](#auth-token)) 100 | - `on.packetError(packet, err, preventCrash)` unable to parse packet. It can mean that agar changed protocol or [issue #46](https://github.com/pulviscriptor/agario-client/issues/46#issuecomment-169764771). By default client will crash. But if you sure this is not protocol change and it don't need [new issue](https://github.com/pulviscriptor/agario-client/issues/) then you need to call `preventCrash()` before callback execution ends. I highly recommend to disconnect `client` if this error happens. 101 | - `on.debugLine(line_x, line_y)` the server sometimes sends a line for the client to render from your ball to the point though don't expect to see it. 102 | - `on.gotLogin()` server authorized you. Missing in original code, check [issue 94](https://github.com/pulviscriptor/agario-client/issues/94) 103 | - `on.logoutRequest()` server forces client to call `window.logout()` 104 | 105 | # Ball API # 106 | `var ball = client.balls[ball_id];` *ball_id* is number that you can get from events 107 | 108 | ## Ball properties ## 109 | Properties that you can change: 110 | 111 | - None. But you can create properties that don't exists for your needs if you want 112 | 113 | Properties that better not to change or you can break something: 114 | 115 | - `ball.id` ID of `Ball` (number) 116 | - `ball.name` nickname of player that own the `Ball` 117 | - `ball.x` last known X coordinate of `Ball` (if `ball.visible` is `true` then its current coordinate) 118 | - `ball.y` last known Y coordinate of `Ball` (if `ball.visible` is `true` then its current coordinate) 119 | - `ball.size` last known size of `Ball` (if `ball.visible` is `true` then its current size) 120 | - `ball.mass` mass of ball calculated from `ball.size` 121 | - `ball.color` string with color of `Ball` 122 | - `ball.virus` if `true` then ball is a virus (green thing that explode big balls) 123 | - `ball.mine` if `true` then we do own this `Ball` 124 | - `ball.client` `Client` that knows this `Ball` (if not `ball.destroyed`) 125 | - `ball.destroyed` if `true` then this `Ball` no more exists, forget about it 126 | - `ball.visible` if `true` then we see this `Ball` on our "screen" (field of view) 127 | - `ball.last_update` timestamp when we last saw this `Ball` 128 | - `ball.update_tick` last tick when we saw this `Ball` 129 | 130 | ## Ball methods ## 131 | - `ball.toString()` will return `ball.id` and `(ball.name)` if set. So you can log `ball` directly 132 | - Other methods is for internal use 133 | 134 | ## Ball events ## 135 | In this list `on.eventName(param1, param2)` means you need to do `ball.on('eventName', function(param1, param2) { ... })` 136 | 137 | - `on.destroy(reason)` `Ball` deleted (check [reasons](#ball-destroy-reasons-list) at the bottom of this document) 138 | - `on.move(old_x, old_y, new_x, new_y)` `Ball` move 139 | - `on.resize(old_size, new_size)` `Ball` resize 140 | - `on.update(old_time, new_time)` new data about `Ball` received 141 | - `on.rename(old_name, new_name)` `Ball` change/set name/we discover name 142 | - `on.appear()` `Ball` appear on "screen" (field of view) 143 | - `on.disappear()` `Ball` disappear from "screen" (field of view) 144 | 145 | # Servers # 146 | When you do `var AgarioClient = require('agario-client');` you can access `AgarioClient.servers` 147 | Functions need `opt` as options object and `cb` as callback function. 148 | 149 | ## Servers options ## 150 | All functions can accept: 151 | `opt.agent` to use for connection. Check [additional info](#socksproxy-support) 152 | `opt.local_address` local interface to bind to for network connections (IP address of interface) 153 | `opt.resolve` set to `true` to resolve IP on client side (since SOCKS4 can't accept domain names) 154 | `opt.ip` if you resolved `m.agar.ip` IP by other way (will cancel `opt.resolve`). 155 | 156 | - `servers.getFFAServer(opt, cb)` to request FFA server. 157 | Needs `opt.region` 158 | - `servers.getTeamsServer(opt, cb)` to request teams server. 159 | Needs `opt.region` 160 | - `servers.getExperimentalServer(opt, cb)` to request experimental server. 161 | Needs `opt.region` 162 | **Use at your own risk!** Support of experimental servers are not guaranteed by agario-client! 163 | - `servers.getPartyServer(opt, cb)` to request party server. 164 | Needs `opt.party_key` 165 | - `servers.createParty(opt, cb)` to request party server. 166 | Needs `opt.region` 167 | 168 | Check [region list](#regions-list) below in this file. 169 | 170 | ## Servers callbacks ## 171 | 172 | Callback will be called with single object that can contain: 173 | - `server` - server's IP:PORT (**add `ws://` before passing to connect()**) 174 | - `key` - server's key 175 | - `error` - error code (`WRONG_HTTP_CODE`/`WRONG_DATA_FORMAT`/`REQUEST_ERROR`/`LOOKUP_FAIL`) 176 | - `error_source` - error object passed from `req.on.error` when available (for example when `REQUEST_ERROR` happens) 177 | - `res` - response object when available (for example when `WRONG_HTTP_CODE` happens) 178 | - `data` - response data string when available (for example when `WRONG_DATA_FORMAT` happens) 179 | 180 | `LOOKUP_FAIL` can happen only if `opt.lookup` was set to `true` and will have only `error_source` 181 | 182 | You can check how `examples/basic.js` uses this. 183 | 184 | # Additional information # 185 | 186 | ## agario-devtools ## 187 | If you want record/repeat or watch in real time what your client doing through web browser, you might want to check [agario-devtools](https://github.com/pulviscriptor/agario-devtools) 188 | 189 | ## Regions list ## 190 | 191 | - BR-Brazil 192 | - CN-China 193 | - EU-London 194 | - JP-Tokyo 195 | - RU-Russia 196 | - SG-Singapore 197 | - TK-Turkey 198 | - US-Atlanta 199 | 200 | ## Ball destroy reasons list ## 201 | - `{'reason': 'reset'}` when `client` destroys everything (connection lost?) 202 | - `{'reason': 'inactive'}` when we didn't saw `Ball` for `client.inactive_destroy` ms 203 | - `{'reason': 'eaten', 'by': ball_id}` when `Ball` got eaten 204 | - `{'reason': 'merge'}` when our `Ball` merges with our other `Ball` 205 | - `{'reason': 'server-forced'}` when server commands to delete all balls 206 | 207 | ## Auth token ## 208 | To login into your account you need to request token. You can check example in `examples/auth_token.js` 209 | First create new `AgarioClient.Account` 210 | ```javascript 211 | var account = new AgarioClient.Account(); 212 | ``` 213 | Then you need to login through facebook on http://agar.io then go to http://www.facebook.com/ and get cookies c_user,datr,xs. 214 | Here is list of properties of `account`: 215 | - **account.c_user** - set to cookie "c_user" from http://www.facebook.com/ 216 | - **account.datr** - set to cookie "datr" from http://www.facebook.com/ 217 | - **account.xs** - set to cookie "xs" from http://www.facebook.com/ 218 | - **account.agent** - agent for connection. Tests shows that you can request token from any IP and then use it on any IP so you don't need SOCKS/Proxy. 219 | - **account.debug** - set **1** to show warnings, otherwise **0**. **Default: 1** 220 | - **account.token_expire** - contains timestamp in milliseconds when token will expire. Tokens are valid for 1-2 hours. If `(+new Date)>account.token_expire` then you need to request new token and use it in new connection to agar. 221 | 222 | Then you call 223 | ```javascript 224 | account.requestFBToken(function(token, info) { 225 | //If you have `token` then you can set it to `client.auth_token` 226 | // and `client.connect()` to agar server 227 | }); 228 | ``` 229 | If `token` is null, then something went wrong. Check `info` which can contain: 230 | - **info.error** - `Error` of connection error 231 | - **info.res** - response's [http.IncomingMessage](https://nodejs.org/api/http.html#http_http_incomingmessage) object 232 | - **info.data** - content of page 233 | 234 | ## SOCKS/Proxy support ## 235 | You can change default agent for `AgarioClient` and `AgarioClient.servers` to use for connections. You can use libs to do it. For testing and example i used [socks](https://www.npmjs.com/package/socks) lib. Execute `node ./node_modules/agario-client/examples/socks.js` to test it and read `examples/socks.js` file to see how to use SOCKS. For proxy you will need to use some other lib. 236 | 237 | ## Adding properties/events ## 238 | You can add your own properties/events to clients/balls. 239 | `var AgarioClient = require('agario-client');` 240 | - Prototype of `Client` is located at `AgarioClient.prototype.` 241 | - Prototype of `Ball` is located at `AgarioClient.Ball.prototype.` 242 | 243 | For example: 244 | ```javascript 245 | AgarioClient.Ball.prototype.isMyFriend = function() { ... }; //to call ball.isMyFriend() 246 | AgarioClient.prototype.addFriend = function(ball_id) { ... }; //to call client.addFriend(1234) 247 | ``` 248 | 249 | Events: 250 | ```javascript 251 | client.on('somebodyAteSomething', function(eater_id, eaten_id) { #eat event 252 | if(client.balls[eaten_id].isMyFriend()) { //if this is my friend 253 | client.emit('friendEaten', eater_id, eaten_id); //emit custom event 254 | } 255 | }); 256 | client.on('friendEaten', function(eater_id, friend_id) { //on friend eaten 257 | client.log('My friend got eaten!'); 258 | }); 259 | ``` 260 | 261 | Check full example in `examples/basic.js` 262 | 263 | ## Feedback ## 264 | If something is broken, please [email me](mailto:pulviscriptor@gmail.com) or [create issue](https://github.com/pulviscriptor/agario-client/issues/new). I will not know that something is broken until somebody will tell me that. 265 | 266 | ## License ## 267 | MIT 268 | -------------------------------------------------------------------------------- /account.js: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var WebSocket = require('ws'); 4 | 5 | var agar_client_id = '677505792353827'; //hardcoded in client 6 | 7 | function Account(name) { //TODO doc vars 8 | this.name = name; //debug name 9 | this.token = null; //token after requestFBToken() 10 | this.token_expire = 0; //timestamp after requestFBToken() 11 | this.token_provider = 1; //provider ID after requester 12 | this.c_user = null; //cookie from www.facebook.com 13 | this.datr = null; //cookie from www.facebook.com 14 | this.xs = null; //cookie from www.facebook.com 15 | this.agent = null; //connection agent 16 | this.debug = 1; 17 | this.server = 'wss://web-live-v3-0.agario.miniclippt.com/ws'; //TODO doc 18 | 19 | this.ws = null; 20 | } 21 | 22 | Account.prototype.log = function(text) { 23 | if(this.name) { 24 | console.log('Account(' + this.name + '): ' + text); 25 | } else { 26 | console.log('Account: ' + text); 27 | } 28 | }; 29 | 30 | //request token from facebook 31 | Account.prototype.requestFBToken = function(cb) { 32 | var account = this; 33 | 34 | if(this.debug >= 1) { 35 | if(!this.c_user) this.log('[warning] You did not specified Account.c_user'); 36 | if(!this.datr) this.log('[warning] You did not specified Account.datr'); 37 | if(!this.xs) this.log('[warning] You did not specified Account.xs'); 38 | } 39 | 40 | var ret = { 41 | error: null, 42 | res: null, 43 | data: null 44 | }; 45 | 46 | var c_user = this.c_user; 47 | var datr = this.datr; 48 | var xs = this.xs; 49 | 50 | //Some users don't decode their cookies, so let's try do it here 51 | var find_hex = /\%[0-9A-F]{2}/i; //Trying to resolve issue #158, using RegEx to find if the string really contains encoded data 52 | if(c_user && c_user.match(find_hex)) c_user = decodeURIComponent(c_user); 53 | if(datr && datr.match(find_hex)) datr = decodeURIComponent(datr); 54 | if(xs && xs.match(find_hex)) xs = decodeURIComponent(xs); 55 | 56 | var cookies = 'c_user=' + encodeURIComponent(c_user) + ';' + 57 | 'datr=' + encodeURIComponent(datr) + ';' + 58 | 'xs=' + encodeURIComponent(xs) + ';'; 59 | 60 | var options = { 61 | host: 'www.facebook.com', 62 | path: '/dialog/oauth?client_id=' + agar_client_id + '&redirect_uri=https://agar.io&scope=public_profile,%20email&response_type=token', 63 | method: 'GET', 64 | headers: { 65 | 'User-Agent': 'Mozilla/5.0', 66 | 'Cookie': cookies 67 | }, 68 | agent: this.agent || null 69 | }; 70 | 71 | var req = https.request(options, function(res) { 72 | var data = ''; 73 | ret.res = res; 74 | 75 | res.setEncoding('utf8'); 76 | res.on('data', function (chunk) { 77 | data += chunk; 78 | }); 79 | res.on('end', function() { 80 | ret.data = data; 81 | 82 | if(res && res.headers && res.headers.location) { 83 | res.headers.location.replace(/access_token=([a-zA-Z0-9-_]*)&/, function(_, parsed_token) { 84 | if(parsed_token) { 85 | account.token = parsed_token; 86 | account.token_provider = 1; 87 | } 88 | }); 89 | res.headers.location.replace(/expires_in=([0-9]*)/, function(_, expire) { 90 | if(expire) { 91 | account.token_expire = (+new Date) + expire*1000; 92 | } 93 | }); 94 | } 95 | 96 | if(cb) cb(account.token, ret); 97 | }); 98 | }); 99 | 100 | req.on('error', function(e) { 101 | ret.error = e; 102 | if(cb) cb(null, ret); 103 | }); 104 | 105 | req.end(); 106 | }; 107 | 108 | module.exports = Account; 109 | 110 | 111 | 112 | //The code below is reserved for future usage, just in case. 113 | /* 114 | Account.prototype.connect = function() { //TODO doc, event 115 | if(this.ws && this.ws.readyState != WebSocket.CLOSED) { 116 | if(this.debug >= 1 && this.ws.readyState == WebSocket.CONNECTING) 117 | this.log('[warning] connect() called while connecting, ignoring'); 118 | 119 | if(this.debug >= 1 && this.ws.readyState == WebSocket.OPEN) 120 | this.log('[warning] connect() called while connected already, ignoring'); 121 | 122 | if(this.debug >= 1 && this.ws.readyState == WebSocket.CLOSING) 123 | this.log('[warning] connect() called while disconnecting, ignoring'); 124 | 125 | return false; 126 | } 127 | 128 | if(!this.token) { 129 | if(this.debug >= 1) 130 | this.log('[warning] connect() called without `client.token` set, ignoring'); 131 | 132 | return false; 133 | } 134 | 135 | if(this.debug >= 1) 136 | this.log('Connecting...'); 137 | 138 | var headers = { 139 | 'Origin': 'http://agar.io' 140 | }; 141 | 142 | this.ws = new WebSocket(this.server, null, {headers: headers, agent: this.agent}); 143 | this.ws.binaryType = "arraybuffer"; 144 | this.ws.onopen = this.onConnect.bind(this); 145 | this.ws.onmessage = this.onMessage.bind(this); 146 | this.ws.onclose = this.onDisconnect.bind(this); 147 | this.ws.onerror = this.onError.bind(this); 148 | 149 | return true; 150 | }; 151 | 152 | Account.prototype.onConnect = function() { //TODO doc, event 153 | if(this.debug >= 1) 154 | this.log('Connected'); 155 | }; 156 | 157 | Account.prototype.onMessage = function() { //TODO doc, event 158 | if(this.debug >= 3) 159 | this.log('Received packet'); 160 | }; 161 | 162 | Account.prototype.onDisconnect = function() { //TODO doc, event 163 | if(this.debug >= 1) 164 | this.log('Disconnected'); 165 | }; 166 | 167 | Account.prototype.onError = function(e) { //TODO doc, event 168 | // http://www.w3.org/TR/2011/WD-websockets-20110419/ 169 | // if(this.ws.readyState == WebSocket.CLOSING) { 170 | if(this.debug >= 1) 171 | this.log('Connection error: ' + e); 172 | }; 173 | 174 | //TODO debug level check before log 175 | 176 | */ 177 | 178 | //For future investigations 179 | /*initial packet: 180 | { 181 | "device": { 182 | "platform": 5, //3=facebook, 4=miniclip, 5=default/other 183 | "version": "1.3.3", //hardcoded as `Ob.VERSION` in agario.js 184 | "width": 0, 185 | "height": 0 186 | }, 187 | "realm": 2, //2 = facebook, 3 = google 188 | "authToken": "CAAJoMB...TI2E2B" 189 | } 190 | 191 | 192 | convert packet to binary: 193 | Rc.encapsulateMessage = function(a, b) { 194 | var c = new qe; 195 | c.set_type(a); 196 | switch (c.get_type()) { 197 | case 30: 198 | c.set_pingField(B.__cast(b, te)); 199 | break; 200 | case 31: 201 | c.set_pongField(B.__cast(b, ue)); 202 | break; 203 | case 70: 204 | c.set_softPurchaseRequestField(B.__cast(b, we)); 205 | null; 206 | break; 207 | case 20: 208 | c.set_disconnectField(B.__cast(b, of)); 209 | null; 210 | break; 211 | case 10: 212 | c.set_loginRequestField(B.__cast(b, PacketToSend)); 213 | break; 214 | case 80: 215 | c.set_updateUserSettingsField(B.__cast(b, xe)); 216 | break; 217 | case 42: 218 | c.set_activateBoostRequestField(B.__cast(b, me)); 219 | break; 220 | case 62: 221 | c.set_gameOverField(B.__cast(b, qf)); 222 | break; 223 | default: 224 | null 225 | } 226 | return Rc.packageMessage(c) 227 | }; 228 | 229 | 230 | readFromSlice: function(a, b) { 231 | for (var c = 0, d = 0, e = 0, f = 0; a.buf.totlen - a.buf.pos > b;) { 232 | var n = q.read__TYPE_UINT32(a); 233 | switch (n >> 3) { 234 | case 1: 235 | if (0 != c) throw new k(new C("Bad data format: Device.platform cannot be set twice.")); 236 | ++c; 237 | this.set_platform(q.read__TYPE_ENUM(a)); 238 | break; 239 | case 2: 240 | if (0 != d) throw new k(new C("Bad data format: Device.version cannot be set twice.")); 241 | ++d; 242 | this.set_version(q.read__TYPE_STRING(a)); 243 | break; 244 | case 3: 245 | if (0 != e) throw new k(new C("Bad data format: Device.width cannot be set twice.")); 246 | ++e; 247 | this.set_width(q.read__TYPE_UINT32(a)); 248 | break; 249 | case 4: 250 | if (0 != f) throw new k(new C("Bad data format: Device.height cannot be set twice.")); 251 | ++f; 252 | this.set_height(q.read__TYPE_UINT32(a)); 253 | break; 254 | default: 255 | y.prototype.readUnknown.call(this, a, n) 256 | } 257 | } 258 | },*/ 259 | -------------------------------------------------------------------------------- /agario-client.js: -------------------------------------------------------------------------------- 1 | var WebSocket = require('ws'); 2 | var Packet = require('./packet.js'); 3 | var servers = require('./servers.js'); 4 | var Account = require('./account.js'); 5 | var EventEmitter = require('events').EventEmitter; 6 | 7 | function Client(client_name) { 8 | //you can change these values 9 | this.client_name = client_name; //name used for log 10 | this.debug = 1; //debug level, 0-5 (5 will output extremely lots of data) 11 | this.inactive_destroy = 5*60*1000; //time in ms when to destroy inactive balls 12 | this.inactive_check = 10*1000; //time in ms when to search inactive balls 13 | this.spawn_interval = 200; //time in ms for respawn interval. 0 to disable (if your custom server don't have spawn problems) 14 | this.spawn_attempts = 25; //how much attempts to spawn before give up (official servers do have unstable spawn problems) 15 | this.agent = null; //agent for connection. Check additional info in README.md 16 | this.local_address = null; //local interface to bind to for network connections (IP address of interface) 17 | this.headers = { //headers for WebSocket connection. 18 | 'Origin': 'http://agar.io' 19 | }; 20 | 21 | //don't change things below if you don't understand what you're doing 22 | 23 | this.tick_counter = 0; //number of ticks (packet ID 16 counter) 24 | this.inactive_interval = 0; //ID of setInterval() 25 | this.balls = {}; //all balls 26 | this.my_balls = []; //IDs of my balls 27 | this.score = 0; //my score 28 | this.leaders = []; //IDs of leaders in FFA mode 29 | this.teams_scores = []; //scores of teams in Teams mode 30 | this.auth_token = ''; //auth token. Check README.md for how to get it 31 | this.auth_provider = 1; //auth provider. 1 = facebook, 2 = google 32 | this.spawn_attempt = 0; //attempt to spawn 33 | this.spawn_interval_id = 0; //ID of setInterval() 34 | } 35 | 36 | Client.prototype = { 37 | connect: function(server, key) { 38 | var opt = { 39 | headers: this.headers 40 | }; 41 | if(this.agent) opt.agent = this.agent; 42 | if(this.local_address) opt.localAddress = this.local_address; 43 | 44 | this.ws = new WebSocket(server, null, opt); 45 | this.ws.binaryType = "arraybuffer"; 46 | this.ws.onopen = this.onConnect.bind(this); 47 | this.ws.onmessage = this.onMessage.bind(this); 48 | this.ws.onclose = this.onDisconnect.bind(this); 49 | this.ws.onerror = this.onError.bind(this); 50 | this.server = server; 51 | this.key = key; 52 | 53 | if(this.debug >= 1) { 54 | if(!key) this.log('[warning] You did not specified "key" for Client.connect(server, key)\n' + 55 | ' If server will not accept you, this may be the problem'); 56 | this.log('connecting...'); 57 | } 58 | 59 | this.emitEvent('connecting'); 60 | }, 61 | 62 | disconnect: function() { 63 | if(this.debug >= 1) 64 | this.log('disconnect() called'); 65 | 66 | if(!this.ws) { 67 | if(this.debug >= 1) 68 | this.log('[warning] disconnect() called before connect(), ignoring this call'); 69 | return false; 70 | } 71 | 72 | this.ws.close(); 73 | return true; 74 | }, 75 | 76 | onConnect: function() { 77 | var client = this; 78 | 79 | if(this.debug >= 1) 80 | this.log('connected to server'); 81 | 82 | this.inactive_interval = setInterval(this.destroyInactive.bind(this), this.inactive_check); 83 | 84 | var buf = new Buffer(5); 85 | buf.writeUInt8(254, 0); 86 | buf.writeUInt32LE(5, 1); 87 | 88 | if(this.ws.readyState !== WebSocket.OPEN) { //`ws` bug https://github.com/websockets/ws/issues/669 `Crash 2` 89 | this.onPacketError(new Packet(buf), new Error('ws bug #669:crash2 detected, `onopen` called with not established connection')); 90 | return; 91 | } 92 | 93 | this.send(buf); 94 | 95 | buf = new Buffer(5); 96 | buf.writeUInt8(255, 0); 97 | buf.writeUInt32LE(154669603, 1); 98 | this.send(buf); 99 | 100 | if(this.key) { 101 | buf = new Buffer(1 + this.key.length); 102 | buf.writeUInt8(80, 0); 103 | for (var i=1;i<=this.key.length;++i) { 104 | buf.writeUInt8(this.key.charCodeAt(i-1), i); 105 | } 106 | this.send(buf); 107 | } 108 | if(this.auth_token) { 109 | var bytes = [102, 8, 1, 18, this.auth_token.length + 25, 1, 8, 10, 82, this.auth_token.length + 20, 1, 10, 13, 8, 5, 18, 5, 49, 46, 52, 46, 57, 24, 0, 32, 0, 16, 2, 26, this.auth_token.length, 1]; 110 | for (var i = 0; i <= this.auth_token.length - 1; i++) { 111 | bytes.push(this.auth_token.charCodeAt(i)); 112 | } 113 | buf = new Buffer(bytes.length); 114 | for (var i = 0; i <= bytes.length - 1; i++) { 115 | buf.writeUInt8(bytes[i], i); 116 | } 117 | this.send(buf); 118 | } 119 | 120 | client.emitEvent('connected'); 121 | }, 122 | 123 | onError: function(e) { 124 | if(this.debug >= 1) 125 | this.log('connection error: ' + e); 126 | 127 | this.emitEvent('connectionError', e); 128 | this.reset(); 129 | }, 130 | 131 | onDisconnect: function() { 132 | if(this.debug >= 1) 133 | this.log('disconnected'); 134 | 135 | this.emitEvent('disconnect'); 136 | this.reset(); 137 | }, 138 | 139 | onMessage: function(e) { 140 | var packet = new Packet(e); 141 | if(!packet.length) { 142 | return this.onPacketError(packet, new Error('Empty packet received')); 143 | } 144 | var packet_id = packet.readUInt8(); 145 | var processor = this.processors[packet_id]; 146 | if(!processor) return this.log('[warning] unknown packet ID(' + packet_id + '): ' + packet.toString()); 147 | 148 | if(this.debug >= 4) 149 | this.log('RECV packet ID=' + packet_id + ' LEN=' + packet.length); 150 | if(this.debug >= 5) 151 | this.log('dump: ' + packet.toString()); 152 | 153 | this.emitEvent('message', packet); 154 | 155 | try { 156 | processor(this, packet); 157 | }catch(err){ 158 | this.onPacketError(packet, err); 159 | } 160 | }, 161 | 162 | // Had to do this because sometimes packets somehow get moving by 1 byte 163 | // https://github.com/pulviscriptor/agario-client/issues/46#issuecomment-169764771 164 | onPacketError: function(packet, err) { 165 | var crash = true; 166 | 167 | this.emitEvent('packetError', packet, err, function() { 168 | crash = false; 169 | }); 170 | 171 | if(crash) { 172 | if(this.debug >= 1) 173 | this.log('Packet error detected! Check packetError event in README.md'); 174 | throw err; 175 | } 176 | }, 177 | 178 | send: function(buf) { 179 | if(this.debug >= 4) 180 | this.log('SEND packet ID=' + buf.readUInt8(0) + ' LEN=' + buf.length); 181 | 182 | if(this.debug >= 5) 183 | this.log('dump: ' + (new Packet(buf).toString())); 184 | 185 | this.ws.send(buf); 186 | }, 187 | 188 | reset: function() { 189 | if(this.debug >= 3) 190 | this.log('reset()'); 191 | 192 | clearInterval(this.inactive_interval); 193 | clearInterval(this.spawn_interval_id); 194 | this.spawn_interval_id = 0; 195 | this.leaders = []; 196 | this.teams_scores = []; 197 | this.my_balls = []; 198 | this.spawn_attempt = 0; 199 | 200 | for(var k in this.balls) if(this.balls.hasOwnProperty(k)) this.balls[k].destroy({'reason':'reset'}); 201 | this.emitEvent('reset'); 202 | }, 203 | 204 | destroyInactive: function() { 205 | var time = (+new Date); 206 | 207 | if(this.debug >= 3) 208 | this.log('destroying inactive balls'); 209 | 210 | for(var k in this.balls) { 211 | if(!this.balls.hasOwnProperty(k)) continue; 212 | var ball = this.balls[k]; 213 | if(time - ball.last_update < this.inactive_destroy) continue; 214 | if(ball.visible) continue; 215 | 216 | if(this.debug >= 3) 217 | this.log('destroying inactive ' + ball); 218 | 219 | ball.destroy({reason: 'inactive'}); 220 | } 221 | }, 222 | 223 | processors: { 224 | //tick 225 | '16': function(client, packet) { 226 | var eaters_count = packet.readUInt16LE(); 227 | 228 | client.tick_counter++; 229 | 230 | //reading eat events 231 | for(var i=0;i= 4) 236 | client.log(eater_id + ' ate ' + eaten_id + ' (' + client.balls[eater_id] + '>' + client.balls[eaten_id] + ')'); 237 | 238 | if(!client.balls[eater_id]) new Ball(client, eater_id); 239 | client.balls[eater_id].update(); 240 | if(client.balls[eaten_id]) client.balls[eaten_id].destroy({'reason':'eaten', 'by':eater_id}); 241 | 242 | client.emitEvent('somebodyAteSomething', eater_id, eaten_id); 243 | } 244 | 245 | //reading actions of balls 246 | while(1) { 247 | var is_virus = false; 248 | var ball_id; 249 | var coordinate_x; 250 | var coordinate_y; 251 | var size; 252 | var color; 253 | var nick = null; 254 | 255 | ball_id = packet.readUInt32LE(); 256 | if(ball_id == 0) break; 257 | coordinate_x = packet.readSInt32LE(); 258 | coordinate_y = packet.readSInt32LE(); 259 | size = packet.readSInt16LE(); 260 | 261 | var color_R = packet.readUInt8(); 262 | var color_G = packet.readUInt8(); 263 | var color_B = packet.readUInt8(); 264 | 265 | color = (color_R << 16 | color_G << 8 | color_B).toString(16); 266 | color = '#' + ('000000' + color).substr(-6); 267 | 268 | var opt = packet.readUInt8(); 269 | is_virus = !!(opt & 1); 270 | var something_1 = !!(opt & 16); //TODO what is this? 271 | 272 | //reserved for future use? 273 | if (opt & 2) { 274 | packet.offset += packet.readUInt32LE(); 275 | } 276 | if (opt & 4) { 277 | var something_2 = ''; //TODO something related to premium skins 278 | while(1) { 279 | var char = packet.readUInt8(); 280 | if(char == 0) break; 281 | if(!something_2) something_2 = ''; 282 | something_2 += String.fromCharCode(char); 283 | } 284 | } 285 | 286 | while(1) { 287 | char = packet.readUInt16LE(); 288 | if(char == 0) break; 289 | if(!nick) nick = ''; 290 | nick += String.fromCharCode(char); 291 | } 292 | 293 | var ball = client.balls[ball_id] || new Ball(client, ball_id); 294 | ball.color = color; 295 | ball.virus = is_virus; 296 | ball.setCords(coordinate_x, coordinate_y); 297 | ball.setSize(size); 298 | if(nick) ball.setName(nick); 299 | ball.update_tick = client.tick_counter; 300 | ball.appear(); 301 | ball.update(); 302 | 303 | if(client.debug >= 5) 304 | client.log('action: ball_id=' + ball_id + ' coordinate_x=' + coordinate_x + ' coordinate_y=' + coordinate_y + ' size=' + size + ' is_virus=' + is_virus + ' nick=' + nick); 305 | 306 | client.emitEvent('ballAction', ball_id, coordinate_x, coordinate_y, size, is_virus, nick); 307 | } 308 | 309 | var balls_on_screen_count = packet.readUInt32LE(); 310 | 311 | //disappear events 312 | for (i=0;i= 4) 334 | client.log('spectate FOV update: x=' + x + ' y=' + y + ' zoom=' + zoom); 335 | 336 | client.emitEvent('spectateFieldUpdate', x, y, zoom); 337 | }, 338 | 339 | '18': function() { 340 | for(var k in this.balls) if(this.balls.hasOwnProperty(k)) this.balls[k].destroy({'reason':'server-forced'}); 341 | }, 342 | 343 | '20': function() { 344 | //i don't know what this is 345 | //in original code it clears our balls array, but i never saw this packet 346 | }, 347 | 348 | //debug line drawn from the player to the specified point 349 | '21': function (client, packet) { 350 | var line_x = packet.readSInt16LE(); 351 | var line_y = packet.readSInt16LE(); 352 | 353 | if (client.debug >= 4) 354 | client.log('debug line drawn from x=' + line_x + ' y=' + line_y); 355 | client.emitEvent('debugLine', line_x, line_y); 356 | }, 357 | 358 | //new ID of your ball (when you join or press space) 359 | '32': function(client, packet) { 360 | var ball_id = packet.readUInt32LE(); 361 | var ball = client.balls[ball_id] || new Ball(client, ball_id); 362 | ball.mine = true; 363 | if(!client.my_balls.length) client.score = 0; 364 | client.my_balls.push(ball_id); 365 | 366 | if(client.debug >= 2) 367 | client.log('my new ball: ' + ball_id); 368 | 369 | if(client.spawn_interval_id) { 370 | if(client.debug >= 4) 371 | client.log('detected new ball, disabling spawn() interval'); 372 | client.spawn_attempt = 0; 373 | clearInterval(client.spawn_interval_id); 374 | client.spawn_interval_id = 0; 375 | } 376 | 377 | client.emitEvent('myNewBall', ball_id); 378 | }, 379 | 380 | //leaderboard update in FFA mode 381 | '49': function(client, packet) { 382 | var highlights = []; 383 | var names = []; 384 | var count = packet.readUInt32LE(); 385 | 386 | for(var i=0;i= 3) 411 | client.log('leaders update: ' + JSON.stringify(highlights) + ',' + JSON.stringify(names)); 412 | 413 | client.emitEvent('leaderBoardUpdate', old_highlights, highlights, old_leaderNames, names); 414 | }, 415 | 416 | //teams scored update in teams mode 417 | '50': function(client, packet) { 418 | var teams_count = packet.readUInt32LE(); 419 | var teams_scores = []; 420 | 421 | for (var i=0;i= 3) 429 | client.log('teams scores update: ' + JSON.stringify(teams_scores)); 430 | 431 | client.teams_scores = teams_scores; 432 | 433 | client.emitEvent('teamsScoresUpdate', old_scores, teams_scores); 434 | }, 435 | 436 | //map size load 437 | '64': function(client, packet) { 438 | var min_x = packet.readFloat64LE(); 439 | var min_y = packet.readFloat64LE(); 440 | var max_x = packet.readFloat64LE(); 441 | var max_y = packet.readFloat64LE(); 442 | 443 | if(client.debug >= 2) 444 | client.log('map size: ' + [min_x, min_y, max_x, max_y].join(',')); 445 | 446 | client.emitEvent('mapSizeLoad', min_x, min_y, max_x, max_y); 447 | }, 448 | 449 | //another unknown packet 450 | '72': function() { 451 | //packet is sent by server but not used in original code 452 | }, 453 | 454 | '81': function(client, packet) { 455 | var level = packet.readUInt32LE(); 456 | var curernt_exp = packet.readUInt32LE(); 457 | var need_exp = packet.readUInt32LE(); 458 | 459 | if(client.debug >= 2) 460 | client.log('experience update: ' + [level, curernt_exp, need_exp].join(',')); 461 | 462 | client.emitEvent('experienceUpdate', level, curernt_exp, need_exp); 463 | }, 464 | 465 | '102': function() { 466 | // This packet used for some shop server wss://web-live-v3-0.agario.miniclippt.com/ws 467 | // There is some "reserved" code for it in "account.js" that you can check. But it is not used since this server is useless for client 468 | // https://github.com/pulviscriptor/agario-client/issues/78 469 | }, 470 | 471 | '103': function(client) { 472 | // Processor for that packet is missing in official client but @SzAmmi reports that he receives it 473 | // https://github.com/pulviscriptor/agario-client/issues/94 474 | client.emit('gotLogin'); 475 | }, 476 | 477 | //server forces client to logout 478 | '104': function(client, packet) { 479 | client.emitEvent('logoutRequest'); 480 | }, 481 | 482 | '240': function(client, packet) { 483 | packet.offset += 4; 484 | var packet_id = packet.readUInt8(); 485 | var processor = client.processors[packet_id]; 486 | if(!processor) return client.log('[warning] unknown packet ID(240->' + packet_id + '): ' + packet.toString()); 487 | processor(client, packet); 488 | }, 489 | 490 | //somebody won, end of the game (server restart) 491 | '254': function(client) { 492 | if(client.debug >= 1) 493 | client.log(client.balls[client.leaders[0]] + ' WON THE GAME! Server going for restart'); 494 | 495 | client.emitEvent('winner', client.leaders[0]); 496 | } 497 | }, 498 | 499 | updateScore: function() { 500 | var potential_score = 0; 501 | for (var i=0;i= 2) 514 | this.log('score: ' + new_score); 515 | 516 | }, 517 | 518 | log: function(msg) { 519 | console.log(this.client_name + ': ' + msg); 520 | }, 521 | 522 | // Fix https://github.com/pulviscriptor/agario-client/issues/95 523 | emitEvent: function() { 524 | var args = []; 525 | for(var i=0;i= 3) 540 | this.log('spawn() called, name=' + name); 541 | 542 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 543 | if(this.debug >= 1) 544 | this.log('[warning] spawn() was called when connection was not established, packet will be dropped'); 545 | return false; 546 | } 547 | 548 | var buf = new Buffer(1 + 2*name.length); 549 | buf.writeUInt8(0, 0); 550 | for (var i=0;i= 4) 558 | this.log('Starting spawn() interval'); 559 | 560 | var that = this; 561 | this.spawn_attempt = 1; 562 | this.spawn_interval_id = setInterval(function() { 563 | if(that.debug >= 4) 564 | that.log('spawn() interval tick, attempt ' + that.spawn_attempt + '/' + that.spawn_attempts); 565 | 566 | if(that.spawn_attempt >= that.spawn_attempts) { 567 | if(that.debug >= 1) 568 | that.log('[warning] spawn() interval gave up! Disconnecting from server!'); 569 | that.spawn_attempt = 0; 570 | clearInterval(that.spawn_interval_id); 571 | that.spawn_interval_id = 0; 572 | that.disconnect(); 573 | return; 574 | } 575 | that.spawn_attempt++; 576 | that.spawn(name); 577 | }, that.spawn_interval); 578 | } 579 | 580 | return true; 581 | }, 582 | 583 | //activate spectate mode 584 | spectate: function() { 585 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 586 | if(this.debug >= 1) 587 | this.log('[warning] spectate() was called when connection was not established, packet will be dropped'); 588 | return false; 589 | } 590 | 591 | var buf = new Buffer([1]); 592 | this.send(buf); 593 | 594 | return true; 595 | }, 596 | 597 | //switch spectate mode (toggle between free look view and leader view) 598 | spectateModeToggle: function() { 599 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 600 | if(this.debug >= 1) 601 | this.log('[warning] spectateModeToggle() was called when connection was not established, packet will be dropped'); 602 | return false; 603 | } 604 | 605 | var buf = new Buffer([18]); 606 | this.send(buf); 607 | var buf = new Buffer([19]); 608 | this.send(buf); 609 | 610 | return true; 611 | }, 612 | 613 | moveTo: function(x, y) { 614 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 615 | if(this.debug >= 1) 616 | this.log('[warning] moveTo() was called when connection was not established, packet will be dropped'); 617 | return false; 618 | } 619 | var buf = new Buffer(13); 620 | buf.writeUInt8(16, 0); 621 | buf.writeInt32LE(Math.round(x), 1); 622 | buf.writeInt32LE(Math.round(y), 5); 623 | buf.writeUInt32LE(0, 9); 624 | this.send(buf); 625 | 626 | return true; 627 | }, 628 | 629 | //split your balls 630 | //they will split in direction that you have set with moveTo() 631 | split: function() { 632 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 633 | if(this.debug >= 1) 634 | this.log('[warning] split() was called when connection was not established, packet will be dropped'); 635 | return false; 636 | } 637 | var buf = new Buffer([17]); 638 | this.send(buf); 639 | 640 | return true; 641 | }, 642 | 643 | //eject some mass 644 | //mass will eject in direction that you have set with moveTo() 645 | eject: function() { 646 | if(!this.ws || this.ws.readyState !== WebSocket.OPEN) { 647 | if(this.debug >= 1) 648 | this.log('[warning] eject() was called when connection was not established, packet will be dropped'); 649 | return false; 650 | } 651 | var buf = new Buffer([21]); 652 | this.send(buf); 653 | 654 | return true; 655 | }, 656 | 657 | //deprecated 658 | set facebook_key(_) { 659 | console.trace('Property "facebook_key" is deprecated. Please check in README.md how new authorization works'); 660 | } 661 | }; 662 | 663 | function Ball(client, id) { 664 | if(client.balls[id]) return client.balls[id]; 665 | 666 | this.id = id; 667 | this.name = null; 668 | this.x = 0; 669 | this.y = 0; 670 | this.size = 0; 671 | this.mass = 0; 672 | this.virus = false; 673 | this.mine = false; 674 | 675 | this.client = client; 676 | this.destroyed = false; 677 | this.visible = false; 678 | this.last_update = (+new Date); 679 | this.update_tick = 0; 680 | 681 | client.balls[id] = this; 682 | return this; 683 | } 684 | Ball.prototype = { 685 | destroy: function(reason) { 686 | this.destroyed = reason; 687 | delete this.client.balls[this.id]; 688 | var mine_ball_index = this.client.my_balls.indexOf(this.id); 689 | if(mine_ball_index > -1) { 690 | this.client.my_balls.splice(mine_ball_index, 1); 691 | this.client.emitEvent('mineBallDestroy', this.id, reason); 692 | if(!this.client.my_balls.length) this.client.emitEvent('lostMyBalls'); 693 | } 694 | 695 | this.emitEvent('destroy', reason); 696 | this.client.emitEvent('ballDestroy', this.id, reason); 697 | }, 698 | 699 | setCords: function(new_x, new_y) { 700 | if(this.x == new_x && this.y == new_y) return; 701 | var old_x = this.x; 702 | var old_y = this.y; 703 | this.x = new_x; 704 | this.y = new_y; 705 | 706 | if(!old_x && !old_y) return; 707 | this.emitEvent('move', old_x, old_y, new_x, new_y); 708 | this.client.emitEvent('ballMove', this.id, old_x, old_y, new_x, new_y); 709 | }, 710 | 711 | setSize: function(new_size) { 712 | if(this.size == new_size) return; 713 | var old_size = this.size; 714 | this.size = new_size; 715 | this.mass = parseInt(Math.pow(new_size/10, 2)); 716 | 717 | if(!old_size) return; 718 | this.emitEvent('resize', old_size, new_size); 719 | this.client.emitEvent('ballResize', this.id, old_size, new_size); 720 | if(this.mine) this.client.updateScore(); 721 | }, 722 | 723 | setName: function(name) { 724 | if(this.name == name) return; 725 | var old_name = this.name; 726 | this.name = name; 727 | 728 | this.emitEvent('rename', old_name, name); 729 | this.client.emitEvent('ballRename', this.id, old_name, name); 730 | }, 731 | 732 | update: function() { 733 | var old_time = this.last_update; 734 | this.last_update = (+new Date); 735 | 736 | this.emitEvent('update', old_time, this.last_update); 737 | this.client.emitEvent('ballUpdate', this.id, old_time, this.last_update); 738 | }, 739 | 740 | appear: function() { 741 | if(this.visible) return; 742 | this.visible = true; 743 | this.emitEvent('appear'); 744 | this.client.emitEvent('ballAppear', this.id); 745 | 746 | if(this.mine) this.client.updateScore(); 747 | }, 748 | 749 | disappear: function() { 750 | if(!this.visible) return; 751 | this.visible = false; 752 | this.emitEvent('disappear'); 753 | this.client.emitEvent('ballDisppear', this.id); //typo https://github.com/pulviscriptor/agario-client/pull/144 754 | this.client.emitEvent('ballDisappear', this.id); 755 | }, 756 | 757 | toString: function() { 758 | if(this.name) return this.id + '(' + this.name + ')'; 759 | return this.id.toString(); 760 | }, 761 | 762 | // Fix https://github.com/pulviscriptor/agario-client/issues/95 763 | emitEvent: function() { 764 | var args = []; 765 | for(var i=0;i 0.5) continue; //if ball is bigger than 50% of our size - then we skip it 110 | var distance = getDistanceBetweenBalls(ball, my_ball); //we calculate distances between our ball and candidate 111 | if(candidate_ball && distance > candidate_distance) continue; //if we do have some candidate and distance to it smaller, than distance to this ball, we skip it 112 | 113 | candidate_ball = ball; //we found new candidate and we record him 114 | candidate_distance = getDistanceBetweenBalls(ball, my_ball); //we record distance to him to compare it with other balls 115 | } 116 | if(!candidate_ball) return; //if we didn't find any candidate, we abort. We will come back here in 100ms later 117 | 118 | client.log('closest ' + candidate_ball + ', distance ' + candidate_distance); 119 | client.moveTo(candidate_ball.x, candidate_ball.y); //we send move command to move to food's coordinates 120 | } 121 | 122 | function getDistanceBetweenBalls(ball_1, ball_2) { //this calculates distance between 2 balls 123 | return Math.sqrt( Math.pow( ball_1.x - ball_2.x, 2) + Math.pow( ball_2.y - ball_1.y, 2) ); 124 | } 125 | 126 | console.log('Requesting server in region ' + region); 127 | AgarioClient.servers.getFFAServer({region: region}, function(srv) { //requesting FFA server 128 | if(!srv.server) return console.log('Failed to request server (error=' + srv.error + ', error_source=' + srv.error_source + ')'); 129 | console.log('Connecting to ' + srv.server + ' with key ' + srv.key); 130 | client.connect('ws://' + srv.server, srv.key); //do not forget to add ws:// 131 | }); 132 | -------------------------------------------------------------------------------- /examples/multiple.js: -------------------------------------------------------------------------------- 1 | //Example of multiple connections in one script 2 | //This code is badly documented, please read basic.js in this folder if you don't understand this code 3 | 4 | var AgarioClient = require('../agario-client.js'); //Use next line in your scripts 5 | //var AgarioClient = require('agario-client'); //Use this in your scripts 6 | 7 | function ExampleBot(bot_id) { 8 | this.bot_id = bot_id; //ID of bot for logging 9 | this.nickname = 'agario-client';//default nickname 10 | this.verbose = true; //default logging enabled 11 | this.interval_id = 0; //here we will store setInterval's ID 12 | 13 | this.server = ''; //server address will be stored here 14 | this.server_key = ''; //server key will be stored here 15 | 16 | this.client = new AgarioClient('Bot ' + this.bot_id); //create new client 17 | this.client.debug = 1; //lets set debug to 1 18 | } 19 | 20 | ExampleBot.prototype = { 21 | log: function(text) { 22 | if(this.verbose) { 23 | console.log(this.bot_id + ' says: ' + text); 24 | } 25 | }, 26 | 27 | connect: function(server, key) { 28 | this.log('Connecting to ' + server + ' with key ' + key); 29 | this.server = server; 30 | this.server_key = key; 31 | this.client.connect(server, key); 32 | this.attachEvents(); 33 | }, 34 | 35 | attachEvents: function() { 36 | var bot = this; 37 | 38 | bot.client.on('connected', function() { 39 | bot.log('Connected, spawning'); 40 | bot.client.spawn(bot.nickname); 41 | //we will search for target to eat every 100ms 42 | bot.interval_id = setInterval(function(){bot.recalculateTarget()}, 100); 43 | }); 44 | 45 | bot.client.on('connectionError', function(e) { 46 | bot.log('Connection failed with reason: ' + e); 47 | bot.log('Server address set to: ' + bot.server + ' key ' + bot.server_key); 48 | }); 49 | 50 | bot.client.on('myNewBall', function(ball_id) { 51 | bot.log('My new ball ' + ball_id); 52 | }); 53 | 54 | bot.client.on('leaderBoardUpdate', function(old_highlights, highlights, old_names, names) { 55 | bot.log('Leaders on server: ' + names.join(', ')); 56 | }); 57 | 58 | bot.client.on('somebodyAteSomething', function(eater_ball, eaten_ball) { 59 | var ball = bot.client.balls[eater_ball]; 60 | if(!ball) return; //if we don't know that ball, we don't care 61 | if(!ball.mine) return; //if it's not our ball, we don't care 62 | bot.client.log('I ate ' + eaten_ball + ', my new size is ' + ball.size); 63 | }); 64 | 65 | bot.client.on('mineBallDestroy', function(ball_id, reason) { //when my ball destroyed 66 | if(reason.by) { 67 | bot.log(bot.client.balls[reason.by] + ' ate my ball'); 68 | } 69 | 70 | if(reason.reason == 'merge') { 71 | bot.log('My ball ' + ball_id + ' merged with my other ball, now i have ' + bot.client.my_balls.length + ' balls'); 72 | }else{ 73 | bot.log('I lost my ball ' + ball_id + ', ' + bot.client.my_balls.length + ' balls left'); 74 | } 75 | }); 76 | 77 | bot.client.on('lostMyBalls', function() { 78 | bot.log('Lost all my balls, respawning'); 79 | bot.client.spawn(bot.nickname); 80 | }); 81 | 82 | bot.client.on('disconnect', function() { 83 | bot.log('Disconnected from server, bye!'); 84 | }); 85 | 86 | bot.client.on('reset', function() { //when client clears everything (connection lost?) 87 | clearInterval(bot.interval_id); 88 | }); 89 | }, 90 | 91 | getDistanceBetweenBalls: function(ball_1, ball_2) { 92 | return Math.sqrt( Math.pow( ball_1.x - ball_2.x, 2) + Math.pow( ball_2.y - ball_1.y, 2) ); 93 | }, 94 | 95 | recalculateTarget: function() { 96 | var bot = this; 97 | var candidate_ball = null; 98 | var candidate_distance = 0; 99 | var my_ball = bot.client.balls[ bot.client.my_balls[0] ]; 100 | if(!my_ball) return; 101 | 102 | for(var ball_id in bot.client.balls) { 103 | var ball = bot.client.balls[ball_id]; 104 | if(ball.virus) continue; 105 | if(!ball.visible) continue; 106 | if(ball.mine) continue; 107 | if(ball.size/my_ball.size > 0.5) continue; 108 | var distance = bot.getDistanceBetweenBalls(ball, my_ball); 109 | if(candidate_ball && distance > candidate_distance) continue; 110 | 111 | candidate_ball = ball; 112 | candidate_distance = bot.getDistanceBetweenBalls(ball, my_ball); 113 | } 114 | if(!candidate_ball) return; 115 | 116 | //bot.log('closest ' + candidate_ball + ', distance ' + candidate_distance); 117 | bot.client.moveTo(candidate_ball.x, candidate_ball.y); 118 | } 119 | }; 120 | 121 | //you can do this in your code to use bot as lib 122 | //module.exports = ExampleBot; 123 | 124 | //launching bots below 125 | 126 | //object of bots 127 | var bots = { 128 | 'Alpha' : null, 129 | 'Bravo' : null, 130 | 'Charlie': null 131 | }; 132 | 133 | //searching party for bots in EU-London 134 | console.log('Requesting party server'); 135 | AgarioClient.servers.createParty({region: 'EU-London'}, function(srv) { 136 | if(!srv.server) return console.log('Failed to request server (error=' + srv.error + ', error_source=' + srv.error_source + ')'); 137 | console.log('Engaging bots to party http://agar.io/#' + srv.key + ' on IP ' + srv.server); 138 | 139 | for(var bot_id in bots) { 140 | bots[bot_id] = new ExampleBot(bot_id); 141 | bots[bot_id].connect('ws://' + srv.server, srv.key); 142 | } 143 | }); 144 | -------------------------------------------------------------------------------- /examples/socks.js: -------------------------------------------------------------------------------- 1 | //This is example of connection to agar.io's server through SOCKS4/SOCKS5 server 2 | 3 | if(process.argv.length < 5) { 4 | var warning = [ 5 | 'Please launch this script like', 6 | ' node ./examples/socks.js SOCKS_VERSION SOCKS_IP SOCKS_PORT', 7 | 'SOCKS_IP - IP of SOCKS server', 8 | 'SOCKS_PORT - port of SOCKS server', 9 | 'SOCKS_VERSION - SOCKS server version. 4/4a/5. You can use "4" for "4a"', 10 | '*This script uses `socks` lib and this is params used by lib', 11 | ]; 12 | 13 | for (var line in warning) console.log( warning[line] ); 14 | process.exit(0); 15 | } 16 | console.log('Example will use SOCKS server ' + process.argv[3] + ':' + process.argv[4] + ' version ' + process.argv[2]); 17 | 18 | //First we need to create agent for connection, you can do it any way with any lib you want. 19 | //I will use `socks` lib https://www.npmjs.com/package/socks 20 | 21 | //var Socks = require('socks'); //DO THIS, not what i do 22 | var Socks; 23 | try { 24 | Socks = require('socks'); 25 | } catch(e) { 26 | var warning = [ 27 | 'Failed to load `socks` lib. Install it in examples path using:', 28 | ' mkdir ./node_modules/examples/node_modules', 29 | ' npm install socks --prefix ./node_modules/agario-client/examples', 30 | ' node ./node_modules/agario-client/examples/socks.js' 31 | ]; 32 | 33 | for (var line in warning) console.log( warning[line] ); 34 | process.exit(0); 35 | } 36 | 37 | //And we need agario-client 38 | var AgarioClient = require('../agario-client.js'); //Use next line in your code 39 | //var AgarioClient = require('agario-client'); //Use this in your code 40 | 41 | //We will need to create new agent for every new connection so we will make function 42 | function createAgent() { 43 | return new Socks.Agent({ 44 | proxy: { 45 | ipaddress: process.argv[3], 46 | port: parseInt(process.argv[4]), 47 | type: parseInt(process.argv[2]) 48 | }} 49 | ); 50 | } 51 | 52 | //Here is main code 53 | 54 | //You need to request server/key and connect to that server from same IP 55 | //So you need to request server/key through same SOCKS server that you will be connecting from 56 | //Create new agent 57 | var agent = createAgent(); 58 | 59 | //Options for getFFAServer 60 | var get_server_opt = { 61 | region: 'EU-London', //server region 62 | agent: agent //our agent 63 | }; 64 | 65 | //SOCKS version 4 do not accept domain names, we will need to resolve it 66 | //SOCKS version 4a and 5 can accept domain names as targets 67 | if(process.argv[2] == '4') { 68 | get_server_opt.resolve = true; 69 | } 70 | 71 | //Requesting server's IP and key 72 | AgarioClient.servers.getFFAServer(get_server_opt, function(srv) { 73 | if(!srv.server) { 74 | console.log('Failed to request server (error=' + srv.error + ', error_source=' + srv.error_source + ')'); 75 | process.exit(0); 76 | } 77 | console.log('Got agar.io server ' + srv.server + ' with key ' + srv.key); 78 | 79 | //Here we already have server and key requested through SOCKS server 80 | //Now we will create agario-client 81 | var client = new AgarioClient('worker'); 82 | client.debug = 2; 83 | 84 | //Create new agent for client 85 | client.agent = createAgent(); 86 | 87 | client.on('leaderBoardUpdate', function(old_highlights, highlights, old_names, names) { 88 | client.log('Leaders on server: ' + names.join(', ')); 89 | console.log('[SUCCESS!] Example succesfully connected to server through SOCKS server and received data. Example is over.'); 90 | client.disconnect(); 91 | }); 92 | 93 | //Connecting to server 94 | client.connect('ws://' + srv.server, srv.key); 95 | }); 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agario-client", 3 | "version": "1.3.8", 4 | "description": "Node.js agar.io client implementation", 5 | "main": "agario-client.js", 6 | "dependencies": { 7 | "ws": "^1.1.0" 8 | }, 9 | "devDependencies": {}, 10 | "files": [ 11 | "agario-client.js", 12 | "packet.js", 13 | "servers.js", 14 | "account.js", 15 | "examples" 16 | ], 17 | "scripts": { 18 | "test": "node examples/basic.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/pulviscriptor/agario-client.git" 23 | }, 24 | "keywords": [ 25 | "agar.io" 26 | ], 27 | "author": "pulviscriptor", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/pulviscriptor/agario-client/issues" 31 | }, 32 | "homepage": "https://github.com/pulviscriptor/agario-client" 33 | } 34 | -------------------------------------------------------------------------------- /packet.js: -------------------------------------------------------------------------------- 1 | //this file is for internal use with packets 2 | 3 | function Packet(e) { 4 | if(e instanceof Buffer) { 5 | this.data = e; 6 | this.length = this.data.length; 7 | }else if((typeof Buffer) != 'undefined' && e.data instanceof Buffer) { 8 | this.data = e.data; 9 | this.length = this.data.length; 10 | }else{ 11 | this.data = new DataView(e.data); 12 | this.length = this.data.byteLength; 13 | } 14 | 15 | this.offset = 0; 16 | } 17 | 18 | Packet.prototype = { 19 | readUInt8: function(p) { 20 | var offset = (typeof p) == 'number' ? p : this.offset; 21 | var ret; 22 | if(this.data.getUint8) { 23 | ret = this.data.getUint8(offset); 24 | }else{ 25 | ret = this.data.readUInt8(offset); 26 | } 27 | if(p === undefined) this.offset += 1; 28 | 29 | return ret; 30 | }, 31 | 32 | readUInt16BE: function(p) { 33 | var offset = (typeof p) == 'number' ? p : this.offset; 34 | var ret; 35 | if(this.data.getUint16) { 36 | ret = this.data.getUint16(offset, false); 37 | }else{ 38 | ret = this.data.readUInt16BE(offset); 39 | } 40 | if(p === undefined) this.offset += 2; 41 | 42 | return ret; 43 | }, 44 | 45 | readUInt16LE: function(p) { 46 | var offset = (typeof p) == 'number' ? p : this.offset; 47 | var ret; 48 | if(this.data.getUint16) { 49 | ret = this.data.getUint16(offset, true); 50 | }else{ 51 | ret = this.data.readUInt16LE(offset); 52 | } 53 | if(p === undefined) this.offset += 2; 54 | 55 | return ret; 56 | }, 57 | 58 | readSInt16LE: function(p) { 59 | var offset = (typeof p) == 'number' ? p : this.offset; 60 | var ret; 61 | if(this.data.getInt16) { 62 | ret = this.data.getInt16(offset, true); 63 | }else{ 64 | ret = this.data.readInt16LE(offset); 65 | } 66 | if(p === undefined) this.offset += 2; 67 | 68 | return ret; 69 | }, 70 | 71 | readUInt32LE: function(p) { 72 | var offset = (typeof p) == 'number' ? p : this.offset; 73 | var ret; 74 | if(this.data.getUint32) { 75 | ret = this.data.getUint32(offset, true); 76 | }else{ 77 | ret = this.data.readUInt32LE(offset); 78 | } 79 | if(p === undefined) this.offset += 4; 80 | 81 | return ret; 82 | }, 83 | 84 | readUInt32BE: function(p) { 85 | var offset = (typeof p) == 'number' ? p : this.offset; 86 | var ret; 87 | if(this.data.getUint32) { 88 | ret = this.data.getUint32(offset, false); 89 | }else{ 90 | ret = this.data.readUInt32BE(offset); 91 | } 92 | if(p === undefined) this.offset += 4; 93 | 94 | return ret; 95 | }, 96 | 97 | readSInt32LE: function(p) { 98 | var offset = (typeof p) == 'number' ? p : this.offset; 99 | var ret; 100 | if(this.data.getInt32) { 101 | ret = this.data.getInt32(offset, true); 102 | }else{ 103 | ret = this.data.readInt32LE(offset); 104 | } 105 | if(p === undefined) this.offset += 4; 106 | 107 | return ret; 108 | }, 109 | 110 | readSInt32BE: function(p) { 111 | var offset = (typeof p) == 'number' ? p : this.offset; 112 | var ret; 113 | if(this.data.getInt32) { 114 | ret = this.data.getInt32(offset, false); 115 | }else{ 116 | ret = this.data.readInt32BE(offset); 117 | } 118 | if(p === undefined) this.offset += 4; 119 | 120 | return ret; 121 | }, 122 | 123 | readFloat32LE: function(p) { 124 | var offset = (typeof p) == 'number' ? p : this.offset; 125 | var ret; 126 | if(this.data.getFloat32) { 127 | ret = this.data.getFloat32(offset, true); 128 | }else{ 129 | ret = this.data.readFloatLE(offset); 130 | } 131 | if(p === undefined) this.offset += 4; 132 | 133 | return ret; 134 | }, 135 | 136 | readFloat32BE: function(p) { 137 | var offset = (typeof p) == 'number' ? p : this.offset; 138 | var ret; 139 | if(this.data.getFloat32) { 140 | ret = this.data.getFloat32(offset, false); 141 | }else{ 142 | ret = this.data.readFloatBE(offset); 143 | } 144 | if(p === undefined) this.offset += 4; 145 | 146 | return ret; 147 | }, 148 | 149 | readFloat64LE: function(p) { 150 | var offset = (typeof p) == 'number' ? p : this.offset; 151 | var ret; 152 | if(this.data.getFloat64) { 153 | ret = this.data.getFloat64(offset, true); 154 | }else{ 155 | ret = this.data.readDoubleLE(offset); 156 | } 157 | if(p === undefined) this.offset += 8; 158 | 159 | return ret; 160 | }, 161 | 162 | readFloat64BE: function(p) { 163 | var offset = (typeof p) == 'number' ? p : this.offset; 164 | var ret; 165 | if(this.data.getFloat64) { 166 | ret = this.data.getFloat64(offset, false); 167 | }else{ 168 | ret = this.data.readDoubleBE(offset); 169 | } 170 | if(p === undefined) this.offset += 8; 171 | 172 | return ret; 173 | }, 174 | 175 | toString: function() { 176 | var out = ''; 177 | for(var i=0;i= 0) 148 | ? opt.party_key.substr(opt.party_key.indexOf('#')+1) 149 | : opt.party_key; 150 | var post_opt = { 151 | url: '/getToken', 152 | data: party_key, 153 | res_data_index: 0, 154 | agent: opt.agent, 155 | ip: opt.ip, 156 | resolve: opt.resolve 157 | }; 158 | servers.postRequest(post_opt, function(res) { 159 | if(!res.server) return cb(res); 160 | res.key = party_key; 161 | cb(res); 162 | }); 163 | } 164 | }; 165 | 166 | module.exports = servers; 167 | 168 | --------------------------------------------------------------------------------