# agario-client #
Node.js client for [agar.io](http://agar.io) with API.

# Outdated! #
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. 
Unfortunately its time to say goodbye to `agario-client`. 
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. 
Thanks to all, it was great time!

## Instructions ##
- Install [Node.js](https://nodejs.org/)
- Install client using `npm install agario-client` (ignore `python` errors)
- Run `node ./node_modules/agario-client/examples/basic.js` (for testing purpose)
- If it works, you're ready to look at API and code

# API #
There are two types of object that have API:

- **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`
- **Ball** - thing that `Client` creates. Everything in game is `Balls` (viruses/food/players...). You can't control `Balls` objects, only observe what they do.

Both objects have same methods for events from [events.EventEmitter](https://nodejs.org/api/events.html):

- `.on('eventName', callback)` attach listener to event
- `.once('eventName', callback)` attach listener to event but execute only once
- `.removeListener('eventName', callback)` remove listener from event
- `.removeAllListeners('eventName')` remove all listeners from event
- `.emit('eventName', p1, p2...)` emit your own event
- check more in [documentation](https://nodejs.org/api/events.html)

# Client API #
    var AgarioClient = require('agario-client');
    var client = new AgarioClient(client_name);
*client_name* is string for client that will be used for logging (if you enable it). It's not your ball name.

## Client properties ##
Properties that you can change:

- `client.debug` debug level. 0-5. 0 is completely silent. 5 is super verbose. **Default: 1**
- `client.server` address that was used in `client.connect()` call
- `client.key` key that was used in `client.connect()` call
- `client.auth_token` token to login. See how to get token in [additional info](#auth-token).
- `client.agent` agent to use for connection. Check [additional info](#socksproxy-support).
- `client.local_address` local interface to bind to for network connections (IP address of interface)
- `client.headers` object with headers for WebSocket connection. **Default: {'Origin':'http://agar.io'}**
- `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)
- `client.inactive_check` time in ms for time interval that search and destroy inactive `Balls`. **Default: 10\*1000** (10 seconds)
- `client.spawn_attempts` how much we need try spawn before disconnect (made for unstable spawn on official servers). **Default: 25**
- `client.spawn_interval` time in ms between spawn attempts. **Default: 200**

Properties that better not to change or you can break something:

- `client.balls` object with all `Balls` that `client` knows about. Access `Ball` like `client.balls[ball_id]`
- `client.my_balls` array of alive `Ball`'s IDs that `client` owns and can control.
- `client.score` personal score since spawn
- `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`)
- `client.teams_scores` array of team's scores in teams mode
- `client.client_name` name that you have set for `client` (not nickname)
- `client.tick_counter` number of *tick* packets received (i call them ticks because they contains information about eating/movement/size/disappear... of `Balls`)

## Client methods ##
- `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
- `client.disconnect()` disconnect from server
- `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.
- `client.spectate()` will activate spectating mode. Look at `client.on('spectateFieldUpdate')` for FOV updates. Will return `false` if connection is not established.
- `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.
- `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.
- `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.
- `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.

## Client events ##
In this list `on.eventName(param1, param2)` means you need to do `client.on('eventName', function(param1, param2) { ... })`

- `on.connecting()` connecting to server
- `on.connected()` connected to server and ready to spawn
- `on.connectionError(err)` connection error
- `on.disconnect()` disconnected
- `on.message(packet)` new packet received from server (check `packet.js`)
- `on.myNewBall(ball_id)` my new `Ball` created (spawn/split/explode...)
- `on.somebodyAteSomething(eater_id, eaten_id)` somebody ate something
- `on.scoreUpdate(old_score, new_score)` personal score updated
- `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)
- `on.teamsScoresUpdate(old_scores, new_scores)` array of teams scores update in teams mode
- `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)
- `on.reset()` when we delete all `Balls` and stop timers (connection lost?)
- `on.winner(ball_id)` somebody won and server going for restart
- `on.ballAction(ball_id, coordinate_x, coordinate_y, size, is_virus, nick)` some action about some `Ball`
- `on.ballAppear(ball_id)` `Ball` appear on "screen" (field of view)
- `on.ballDisappear(ball_id)` `Ball` disappear from "screen" (field of view)
- `on.ballDestroy(ball_id, reason)` `Ball` deleted (check [reasons](#ball-destroy-reasons-list) at the bottom of this document)
- `on.mineBallDestroy(ball_id, reason)` mine (your) `Ball` deleted (check reasons at the bottom of this document)
- `on.lostMyBalls()` all mine `Balls` destroyed/eaten
- `on.merge(destroyed_ball_id)` mine two `Balls` connects into one
- `on.ballMove(ball_id, old_x, old_y, new_x, new_y)` `Ball` move
- `on.ballResize(ball_id, old_size, new_size)` `Ball` resize
- `on.ballRename(ball_id, old_name, new_name)` `Ball` set name/change name/we discover name
- `on.ballUpdate(ball_id, old_update_time, new_update_time)` new data about ball received
- `on.spectateFieldUpdate(cord_x, cord_y, zoom_level)` coordinates of field of view in `client.spectate()` mode
- `on.experienceUpdate(level, current_exp, need_exp)` experience information update (if logined with [auth token](#auth-token))
- `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). <b>By default client will crash.</b> 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 <b>recommend to disconnect</b> `client` if this error happens.
- `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.
- `on.gotLogin()` server authorized you. Missing in original code, check [issue 94](https://github.com/pulviscriptor/agario-client/issues/94)
- `on.logoutRequest()` server forces client to call `window.logout()`

# Ball API #
`var ball = client.balls[ball_id];` *ball_id* is number that you can get from events

## Ball properties ##
Properties that you can change:

- None. But you can create properties that don't exists for your needs if you want

Properties that better not to change or you can break something:

- `ball.id` ID of `Ball` (number)
- `ball.name` nickname of player that own the `Ball`
- `ball.x` last known X coordinate of `Ball` (if `ball.visible` is `true` then its current coordinate)
- `ball.y` last known Y coordinate of `Ball` (if `ball.visible` is `true` then its current coordinate)
- `ball.size` last known size of `Ball` (if `ball.visible` is `true` then its current size)
- `ball.mass` mass of ball calculated from `ball.size`
- `ball.color` string with color of `Ball`
- `ball.virus` if `true` then ball is a virus (green thing that explode big balls)
- `ball.mine` if `true` then we do own this `Ball`
- `ball.client` `Client` that knows this `Ball` (if not `ball.destroyed`)
- `ball.destroyed` if `true` then this `Ball` no more exists, forget about it
- `ball.visible` if `true` then we see this `Ball` on our "screen" (field of view)
- `ball.last_update` timestamp when we last saw this `Ball`
- `ball.update_tick` last tick when we saw this `Ball`

## Ball methods ##
- `ball.toString()` will return `ball.id` and `(ball.name)` if set. So you can log `ball` directly
- Other methods is for internal use

## Ball events ##
In this list `on.eventName(param1, param2)` means you need to do `ball.on('eventName', function(param1, param2) { ... })`

 - `on.destroy(reason)` `Ball` deleted (check [reasons](#ball-destroy-reasons-list) at the bottom of this document)
 - `on.move(old_x, old_y, new_x, new_y)` `Ball` move
 - `on.resize(old_size, new_size)` `Ball` resize
 - `on.update(old_time, new_time)` new data about `Ball` received
 - `on.rename(old_name, new_name)` `Ball` change/set name/we discover name
 - `on.appear()` `Ball` appear on "screen" (field of view)
 - `on.disappear()` `Ball` disappear from "screen" (field of view)

# Servers #
When you do `var AgarioClient = require('agario-client');` you can access `AgarioClient.servers`
Functions need `opt` as options object and `cb` as callback function.

## Servers options ##
All functions can accept:
`opt.agent` to use for connection. Check [additional info](#socksproxy-support)
`opt.local_address` local interface to bind to for network connections (IP address of interface)
`opt.resolve` set to `true` to resolve IP on client side (since SOCKS4 can't accept domain names)
`opt.ip` if you resolved `m.agar.ip` IP by other way (will cancel `opt.resolve`).

- `servers.getFFAServer(opt, cb)` to request FFA server.  
  Needs `opt.region`
- `servers.getTeamsServer(opt, cb)` to request teams server.  
  Needs `opt.region`
- `servers.getExperimentalServer(opt, cb)` to request experimental server.  
  Needs `opt.region`  
  **Use at your own risk!** Support of experimental servers are not guaranteed by agario-client!
- `servers.getPartyServer(opt, cb)` to request party server.  
  Needs `opt.party_key`
- `servers.createParty(opt, cb)` to request party server.  
  Needs `opt.region`

Check [region list](#regions-list) below in this file.

## Servers callbacks ##

Callback will be called with single object that can contain:
- `server` - server's IP:PORT (**add `ws://` before passing to connect()**)
- `key` - server's key
- `error` - error code (`WRONG_HTTP_CODE`/`WRONG_DATA_FORMAT`/`REQUEST_ERROR`/`LOOKUP_FAIL`)
- `error_source` - error object passed from `req.on.error` when available (for example when `REQUEST_ERROR` happens)
- `res` - response object when available (for example when `WRONG_HTTP_CODE` happens)
- `data` - response data string when available (for example when `WRONG_DATA_FORMAT` happens)

`LOOKUP_FAIL` can happen only if `opt.lookup` was set to `true` and will have  only `error_source`

You can check how `examples/basic.js` uses this.

# Additional information #

## agario-devtools ##
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)

## Regions list ##

- BR-Brazil
- CN-China
- EU-London
- JP-Tokyo
- RU-Russia
- SG-Singapore
- TK-Turkey
- US-Atlanta

## Ball destroy reasons list ##
- `{'reason': 'reset'}` when `client` destroys everything (connection lost?)
- `{'reason': 'inactive'}` when we didn't saw `Ball` for `client.inactive_destroy` ms
- `{'reason': 'eaten', 'by': ball_id}` when `Ball` got eaten
- `{'reason': 'merge'}` when our `Ball` merges with our other `Ball`
- `{'reason': 'server-forced'}` when server commands to delete all balls

## Auth token ##
To login into your account you need to request token. You can check example in `examples/auth_token.js` 
First create new `AgarioClient.Account`
```javascript
var account = new AgarioClient.Account();
```
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. 
Here is list of properties of `account`: 
- **account.c_user** - set to cookie "c_user" from http://www.facebook.com/
- **account.datr** - set to cookie "datr" from http://www.facebook.com/
- **account.xs** - set to cookie "xs" from http://www.facebook.com/
- **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.
- **account.debug** - set **1** to show warnings, otherwise **0**. **Default: 1**
- **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.

Then you call 
```javascript
account.requestFBToken(function(token, info) {
	//If you have `token` then you can set it to `client.auth_token` 
    // and `client.connect()` to agar server
});
```
If `token` is null, then something went wrong. Check `info` which can contain: 
- **info.error** - `Error` of connection error
- **info.res** - response's [http.IncomingMessage](https://nodejs.org/api/http.html#http_http_incomingmessage) object
- **info.data** - content of page

## SOCKS/Proxy support ##
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.

## Adding properties/events ##
You can add your own properties/events to clients/balls.
`var AgarioClient = require('agario-client');`
- Prototype of `Client` is located at `AgarioClient.prototype.`
- Prototype of `Ball` is located at `AgarioClient.Ball.prototype.`

For example:
```javascript
AgarioClient.Ball.prototype.isMyFriend = function() { ... };  //to call ball.isMyFriend()
AgarioClient.prototype.addFriend = function(ball_id) { ... }; //to call client.addFriend(1234)
```

Events:
```javascript
client.on('somebodyAteSomething', function(eater_id, eaten_id) {  #eat event
    if(client.balls[eaten_id].isMyFriend()) { //if this is my friend
        client.emit('friendEaten', eater_id, eaten_id); //emit custom event
    }
});
client.on('friendEaten', function(eater_id, friend_id) { //on friend eaten
    client.log('My friend got eaten!');
});
```

Check full example in `examples/basic.js`

## Feedback ##
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.

## License ##
MIT
