summaryrefslogtreecommitdiffstats
path: root/telepot/api.py
diff options
context:
space:
mode:
Diffstat (limited to 'telepot/api.py')
-rw-r--r--telepot/api.py164
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