Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 76 additions & 85 deletions coc/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,23 @@
import logging
import aiohttp
import asyncio
import signal
import sys
import functools
import time

from contextlib import contextmanager
from functools import wraps
from threading import Thread
from urllib.parse import urlencode
from itertools import cycle
from datetime import datetime
from collections import deque

from .errors import HTTPException, Maitenance, NotFound, InvalidArgument, Forbidden, InvalidCredentials
from .errors import HTTPException, Maitenance, NotFound, InvalidArgument, Forbidden

log = logging.getLogger(__name__)
KEY_MINIMUM, KEY_MAXIMUM = 1, 10


def timeout(seconds, error_message='Client timed out.'):
def decorated(func):
def _handle_timeout(signum, frame):
raise TimeoutError(error_message)

def wrapper(*args, **kwargs):
if sys.platform == 'win32':
# TODO: Fix for windows
# for now just return function and ignore the problem
return func(*args, **kwargs)

signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0)
return result

return functools.wraps(func)(wrapper)
return decorated


async def json_or_text(response):
try:
ret = await response.json()
Expand Down Expand Up @@ -132,7 +111,6 @@ def __init__(self, method, path, kwargs: dict=None, api_page=False):
else:
self.url = url


class HTTPClient:
def __init__(self, client, loop, email, password,
key_names, key_count, throttle_limit):
Expand All @@ -149,9 +127,11 @@ def __init__(self, client, loop, email, password,
self.__lock = asyncio.Semaphore(per_second)
self.__throttle = Throttler(per_second, loop=self.loop)

asyncio.ensure_future(self.get_keys())
__key_loop__ = asyncio.new_event_loop()
__key_loop__.run_until_complete(self.get_keys(__key_loop__))


async def get_keys(self):
async def get_keys(self, loop):
self.__session = aiohttp.ClientSession(loop=self.loop)

key_count = self.key_count
Expand Down Expand Up @@ -183,18 +163,13 @@ async def get_keys(self):
async def close(self):
if self.__session:
await self.__session.close()

@timeout(60, "Client timed out while attempting to establish a connection to the Developer Portal")
async def ensure_logged_in(self):
while not hasattr(self, 'keys'):
await asyncio.sleep(0.1)

async def request(self, route, **kwargs):
method = route.method
url = route.url

if 'headers' not in kwargs:
await self.ensure_logged_in()
#self.ensure_logged_in()
key = next(self.keys)

headers = {
Expand All @@ -207,45 +182,61 @@ async def request(self, route, **kwargs):
kwargs['headers']['Content-Type'] = 'application/json'

async with self.__lock:
async with self.__throttle, self.__session.request(method, url, **kwargs) as r:
log.debug('%s (%s) has returned %s', url, method, r.status)
data = await json_or_text(r)
log.debug(data)

if 200 <= r.status < 300:
log.debug('%s has received %s', url, data)
return data

if r.status == 400:
raise InvalidArgument(r, data)

if r.status == 403:
if data.get('reason') in ['accessDenied.invalidIp']:
if 'key' in locals():
await self.reset_key(key)
log.info('Reset Clash of Clans key')
return await self.request(route, **kwargs)

raise Forbidden(r, data)

if r.status == 404:
raise NotFound(r, data)
if r.status == 429:
log.error('We have been rate-limited by the API. '
'Reconsider the number of requests you are allowing per second.')
raise HTTPException(r, data)

if r.status == 503:
raise Maitenance(r, data)
else:
raise HTTPException(r, data)
with self.get_correct_loop_session() as session:
async with self.__throttle, session.request(method, url, **kwargs) as r:
log.debug('%s (%s) has returned %s', url, method, r.status)
data = await json_or_text(r)
log.debug(data)

if 200 <= r.status < 300:
log.debug('%s has received %s', url, data)
return data

if r.status == 400:
raise InvalidArgument(r, data)

if r.status == 403:
if data.get('reason') in ['accessDenied.invalidIp']:
if 'key' in locals():
await self.reset_key(key)
log.info('Reset Clash of Clans key')
return await self.request(route, **kwargs)

raise Forbidden(r, data)

if r.status == 404:
raise NotFound(r, data)
if r.status == 429:
log.error('We have been rate-limited by the API. '
'Reconsider the number of requests you are allowing per second.')
raise HTTPException(r, data)

if r.status == 503:
raise Maitenance(r, data)
else:
raise HTTPException(r, data)

@contextmanager
def get_correct_loop_session(self):
loop = asyncio.get_event_loop()
try:
session = None
if loop != self.__session:
session = aiohttp.ClientSession(loop=loop)
yield session
else:
yield self.__session
finally:
if session:
asyncio.ensure_future(session.close())

async def get_ip(self):
async with self.__session.request('GET', 'http://ip.42.pl/short') as r:
log.debug('%s (%s) has returned %s', 'http://ip.42.pl/short', 'GET', r.status)
ip = await r.text()
log.debug('%s has received %s', 'http://ip.42.pl/short', ip)
return ip
with self.get_correct_loop_session() as session:
async with session.request('GET', 'http://ip.42.pl/short') as r:
log.debug('%s (%s) has returned %s', 'http://ip.42.pl/short', 'GET', r.status)
ip = await r.text()
log.debug('%s has received %s', 'http://ip.42.pl/short', ip)
return ip

@staticmethod
def create_cookies(response_dict, session):
Expand Down Expand Up @@ -361,15 +352,14 @@ async def login_to_site(self, email, password):
headers = {
'content-type': 'application/json'
}
async with self.__session.post('https://developer.clashofclans.com/api/login',
json=login_data, headers=headers) as sess:
response_dict = await sess.json()
log.debug('%s has received %s', 'https://developer.clashofclans.com/api/login',
response_dict)
if sess.status == 403:
raise InvalidCredentials(sess, response_dict)
with self.get_correct_loop_session() as session:
async with session.post('https://developer.clashofclans.com/api/login',
json=login_data, headers=headers) as sess:
response_dict = await sess.json()
log.debug('%s has received %s', 'https://developer.clashofclans.com/api/login',
response_dict)

session = sess.cookies.get('session').value
session = sess.cookies.get('session').value

return response_dict, session

Expand All @@ -378,11 +368,12 @@ async def find_site_keys(self, cookies):
"cookie": cookies,
"content-type": "application/json"
}
async with self.__session.post('https://developer.clashofclans.com/api/apikey/list',
data=json.dumps({}), headers=headers) as sess:
existing_keys_dict = await sess.json()
log.debug('%s has received %s', 'https://developer.clashofclans.com/api/apikey/list',
existing_keys_dict)
with self.get_correct_loop_session() as session:
async with session.post('https://developer.clashofclans.com/api/apikey/list',
data=json.dumps({}), headers=headers) as sess:
existing_keys_dict = await sess.json()
log.debug('%s has received %s', 'https://developer.clashofclans.com/api/apikey/list',
existing_keys_dict)

return existing_keys_dict

Expand Down