Source code for pyagar.client

"""
``pyagar.client``
=================

This module contains the Client class.

"""
# pylint: disable=I0011,C0103
import base64
import random
import struct
import asyncio
import requests


from pyagar.log import logger


INIT_TOKEN = '154669603'
PROTO_VERSION = 5
USER_AGENT = (
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
    'Chrome/43.0.2357.125 Safari/537.36')

#
# <monkeypatch>
# Monkeypatch the websockets library to bypass agar.io "limitations".
#
from websockets import client

[docs]def build_request(set_header): """ Build a handshake request to send to the server. Return the `key` which must be passed to :func:`check_response`. """ rand = bytes(random.getrandbits(8) for _ in range(16)) key = base64.b64encode(rand).decode() set_header('Connection', 'Upgrade') set_header('Sec-WebSocket-Extensions', 'permessage-deflate; client_max_window_bits') set_header('Sec-WebSocket-Key', key) set_header('Sec-WebSocket-Version', '13') set_header('Upgrade', 'websocket') return key
client.USER_AGENT = USER_AGENT client.build_request = build_request # # </monkeypatch> # from pyagar import messages import websockets
[docs]class Client: """ The client. Manages the connection, receives the data from the server and sends back any command requested by the player. """ def __init__(self, nick, region='EU-London', party=False): self.nick = nick self.region = region self.server = None self.token = None self.party = party if self.party is True: self.create_party() self.ws = None self.connected = asyncio.Event() self.messages = asyncio.Queue()
[docs] def create_party(self): """Create a new party.""" logger.info("Creating a new party.") region = b":".join((self.region.encode('ascii'), b"party")) data = b"\n".join((region, INIT_TOKEN.encode('ascii'))) res = requests.post('https://m.agar.io/', data=data, headers={'Origin': 'http://agar.io', 'User-Agent': USER_AGENT, 'Referer': 'http://agar.io/'}) self.server, self.token, _ = res.text.split('\n') logger.debug("Server: %s", self.server) logger.info("Party Token: %s", self.token)
@classmethod
[docs] def get_regions(cls): """Request the list of regions.""" res = requests.get('https://m.agar.io/info') return res.json().get('regions', {})
[docs] def get_server(self): """Requests a new server and token.""" if not self.party: url = 'https://m.agar.io/' data = b"\n".join((self.region.encode('ascii'), INIT_TOKEN.encode('ascii'))) else: url = 'https://m.agar.io/getToken' data = self.party.encode("ascii") res = requests.post(url, data=data, headers={'Origin': 'http://agar.io', 'User-Agent': USER_AGENT, 'Referer': 'http://agar.io/'}) if not self.party: self.server, self.token, _ = res.text.split('\n') else: self.server = res.text.strip('\n') self.token = self.party logger.debug("Server: %s", self.server) logger.debug("Token: %s", self.token)
@asyncio.coroutine
[docs] def connect(self): """Connects to the server.""" if self.server is None: self.get_server() logger.info("Connecting to server %s", self.server) self.ws = yield from websockets.connect("ws://" + self.server, origin='http://agar.io') yield from self.ws.send(struct.pack("<BI", 254, PROTO_VERSION)) yield from self.ws.send(struct.pack("<BI", 255, int(INIT_TOKEN))) # Send token msg = struct.pack("B" + ("B" * len(self.token)), 80, *[ord(c) for c in self.token]) yield from self.ws.send(msg) logger.debug("Connected!") self.connected.set()
@asyncio.coroutine
[docs] def spawn(self): """Sends the ``spawn`` command.""" yield from self.connected.wait() rawnick = self.nick.encode('utf-8') msg = struct.pack("<B" + ("H" * len(rawnick)), 0, *rawnick) yield from self.ws.send(msg) logger.debug("Spawn sent.")
@asyncio.coroutine
[docs] def split(self): """Sends the ``split cell`` command.""" yield from self.connected.wait() msg = struct.pack("<B", 17) yield from self.ws.send(msg) logger.debug("Split sent.")
@asyncio.coroutine
[docs] def eject(self): """Sends the ``mass eject`` command.""" yield from self.connected.wait() msg = struct.pack("<B", 21) yield from self.ws.send(msg) logger.debug("Eject sent.")
@asyncio.coroutine
[docs] def read(self): """Read, decode and queue data packets from the server.""" while True: yield from self.connected.wait() data = yield from self.ws.recv() if data is None: self.connected.clear() self.server = self.token = None yield from self.connect() continue msg = messages.MSG(data) if msg.data is None: logger.warning("Unknown message %r", msg) else: yield from self.messages.put(msg.data)
@asyncio.coroutine
[docs] def move(self, x, y): """Sends the ``movement`` command.""" yield from self.connected.wait() yield from self.ws.send(struct.pack("<BddI", 16, x, y, 0)) logger.debug("Move sent (x=%s, y=%s)", x, y)
@asyncio.coroutine
[docs] def spectate(self): """Initiates the spectator mode.""" yield from self.connected.wait() yield from asyncio.sleep(2) yield from self.ws.send(struct.pack("B", 1)) logger.debug("Spectate sent.")