diff options
Diffstat (limited to 'telepot/api.py')
| -rw-r--r-- | telepot/api.py | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/telepot/api.py b/telepot/api.py new file mode 100644 index 0000000..3ffa788 --- /dev/null +++ b/telepot/api.py @@ -0,0 +1,164 @@ +import urllib3 +import logging +import json +import re +import os + +from . import exception, _isstring + +# Suppress InsecurePlatformWarning +urllib3.disable_warnings() + + +_default_pool_params = dict(num_pools=3, maxsize=10, retries=3, timeout=30) +_onetime_pool_params = dict(num_pools=1, maxsize=1, retries=3, timeout=30) + +_pools = { + 'default': urllib3.PoolManager(**_default_pool_params), +} + +_onetime_pool_spec = (urllib3.PoolManager, _onetime_pool_params) + + +def set_proxy(url, basic_auth=None): + """ + Access Bot API through a proxy. + + :param url: proxy URL + :param basic_auth: 2-tuple ``('username', 'password')`` + """ + global _pools, _onetime_pool_spec + if not url: + _pools['default'] = urllib3.PoolManager(**_default_pool_params) + _onetime_pool_spec = (urllib3.PoolManager, _onetime_pool_params) + elif basic_auth: + h = urllib3.make_headers(proxy_basic_auth=':'.join(basic_auth)) + _pools['default'] = urllib3.ProxyManager(url, proxy_headers=h, **_default_pool_params) + _onetime_pool_spec = (urllib3.ProxyManager, dict(proxy_url=url, proxy_headers=h, **_onetime_pool_params)) + else: + _pools['default'] = urllib3.ProxyManager(url, **_default_pool_params) + _onetime_pool_spec = (urllib3.ProxyManager, dict(proxy_url=url, **_onetime_pool_params)) + +def _create_onetime_pool(): + cls, kw = _onetime_pool_spec + return cls(**kw) + +def _methodurl(req, **user_kw): + token, method, params, files = req + return 'https://api.telegram.org/bot%s/%s' % (token, method) + +def _which_pool(req, **user_kw): + token, method, params, files = req + return None if files else 'default' + +def _guess_filename(obj): + name = getattr(obj, 'name', None) + if name and _isstring(name) and name[0] != '<' and name[-1] != '>': + return os.path.basename(name) + +def _filetuple(key, f): + if not isinstance(f, tuple): + return (_guess_filename(f) or key, f.read()) + elif len(f) == 1: + return (_guess_filename(f[0]) or key, f[0].read()) + elif len(f) == 2: + return (f[0], f[1].read()) + elif len(f) == 3: + return (f[0], f[1].read(), f[2]) + else: + raise ValueError() + +import sys +PY_3 = sys.version_info.major >= 3 +def _fix_type(v): + if isinstance(v, float if PY_3 else (long, float)): + return str(v) + else: + return v + +def _compose_fields(req, **user_kw): + token, method, params, files = req + + fields = {k:_fix_type(v) for k,v in params.items()} if params is not None else {} + if files: + fields.update({k:_filetuple(k,v) for k,v in files.items()}) + + return fields + +def _default_timeout(req, **user_kw): + name = _which_pool(req, **user_kw) + if name is None: + return _onetime_pool_spec[1]['timeout'] + else: + return _pools[name].connection_pool_kw['timeout'] + +def _compose_kwargs(req, **user_kw): + token, method, params, files = req + kw = {} + + if not params and not files: + kw['encode_multipart'] = False + + if method == 'getUpdates' and params and 'timeout' in params: + # Ensure HTTP timeout is longer than getUpdates timeout + kw['timeout'] = params['timeout'] + _default_timeout(req, **user_kw) + elif files: + # Disable timeout if uploading files. For some reason, the larger the file, + # the longer it takes for the server to respond (after upload is finished). + # It is unclear how long timeout should be. + kw['timeout'] = None + + # Let user-supplied arguments override + kw.update(user_kw) + return kw + +def _transform(req, **user_kw): + kwargs = _compose_kwargs(req, **user_kw) + + fields = _compose_fields(req, **user_kw) + + url = _methodurl(req, **user_kw) + + name = _which_pool(req, **user_kw) + + if name is None: + pool = _create_onetime_pool() + else: + pool = _pools[name] + + return pool.request_encode_body, ('POST', url, fields), kwargs + +def _parse(response): + try: + text = response.data.decode('utf-8') + data = json.loads(text) + except ValueError: # No JSON object could be decoded + raise exception.BadHTTPResponse(response.status, text, response) + + if data['ok']: + return data['result'] + else: + description, error_code = data['description'], data['error_code'] + + # Look for specific error ... + for e in exception.TelegramError.__subclasses__(): + n = len(e.DESCRIPTION_PATTERNS) + if any(map(re.search, e.DESCRIPTION_PATTERNS, n*[description], n*[re.IGNORECASE])): + raise e(description, error_code, data) + + # ... or raise generic error + raise exception.TelegramError(description, error_code, data) + +def request(req, **user_kw): + fn, args, kwargs = _transform(req, **user_kw) + r = fn(*args, **kwargs) # `fn` must be thread-safe + return _parse(r) + +def _fileurl(req): + token, path = req + return 'https://api.telegram.org/file/bot%s/%s' % (token, path) + +def download(req, **user_kw): + pool = _create_onetime_pool() + r = pool.request('GET', _fileurl(req), **user_kw) + return r |
