update
This commit is contained in:
164
lib/support/telepot2/api.py
Normal file
164
lib/support/telepot2/api.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user