├── .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 |
--------------------------------------------------------------------------------